Photo by hannah joshua on Unsplash
Where do I put my business logic in Rails?
Using the Command Pattern to achieve slimmer controllers
Table of contents
Bigger apps means more complex business logic, and questioning: where do I put all that business logic?. Having conventions or Patterns to approach such complexity can alleviate the stress that comes when growing our code base.
For this article we will avoid the usual controversy about where should business logic live (Controllers or Models), and thus we will focus on a practical example with bloated controllers. The same logic can be applied to having the logic inside Models.
Problem
To avoid having huge controllers with all kinds of instructions living inside them, we can follow different strategies. One of them is using the Command Pattern, in which we create classes that perform all the instructions related to a process.
To make this more clear, let's think we are building a Blog. Inside the ArticlesController
we could have the following create
action to create an Article:
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
def create
@article = Article.new(article_params)
if @article.save
notify_followers
update_author_rank
redirect_to @article
else
render :new, status: :unprocessable_entity
end
end
private
# Params whitelist for article creation
def article_params
params.require(:article).permit( ... )
end
# Sends mails to author's follower's notifying
# about the new post in the blog
def notify_followers
...
end
# Update author's statistics to include the new post
def update_author_rank
...
end
end
Notice how many lines of code we have just to complete one thing: create a blog post. Imagine now we have to add the remaining actions in the controller to make all CRUD operations: index, show, new, delete
, etc.
Command Pattern to the rescue
To tidy up the previous example, we could make a PORO (Plain Old Ruby Object, in simple terms: a simple ruby class) in charge of taking all the necessary steps to create an article and just call it from the controller, like so:
# app/services/article/create.rb
# Class following Command Pattern in charge of
# taking all the necessary steps to create a new
# `Article` object.
class Article::Create
def initialize params
@params = params
end
# This method is the core of the PORO, it is the one
# that defines the process.
# By convention it is the only public method of the class,
# besides the initializer
def call
@article = Article.new @params
if @article.save
notify_followers
update_author_rank
end
@article
end
private
# Sends mails to author's follower's notifying
# about the new post in the blog
def notify_followers
...
end
# Update author's statistics to include the new post
def update_author_rank
...
end
end
# app/controllers/articles_controller.rb
class ArticlesControllers < ApplicationController
def create
@article = Article::Create.new(article_params).call
end
private
# Params whitelist for article creation
def article_params
params.require(:article).permit( ... )
end
end
Now, when we want to assess a bug in the creation of an article we know that it will (most probably) be in the Article::Create
class. and if we have a problem with how the app responds to the user it should be in the controller.
Another benefit of this approach is that is we have other modules that need to create articles they can call the service and we don't need to duplicate all the logic. Think of a versioned API (Application Programming Interface), of mass importers, etc.
Conclusion
The Command Pattern allows us to delegate responsibility to specific classes, and therefore having out controllers (or models) cleaner. Having a good delegation of responsibilities is key to build maintainable applications.