Where do I put my business logic in Rails?

Using the Command Pattern to achieve slimmer controllers

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.