Pragmatic Pineapple 🍍

Ruby on Rails Controller Patterns and Anti-patterns

Published Last updated loading views

Welcome back to the fourth installment of the Ruby on Rails Patterns and Anti-Patterns series.

Previously, we covered patterns and anti-patterns in general as well as in relation to Rails Models and Views. In this post, we are going to analyze the final part of the MVC (Model-View-Controller) design pattern — the Controller. Let’s dive in and go through the patterns and anti-patterns related to Rails Controllers.

At The Front Lines

Since Ruby on Rails is a web framework, HTTP requests are a vital part of it. All sorts of Clients reach out to Rails backends via requests and this is where controllers shine. Controllers are at the front lines of receiving and handling requests. That makes them a fundamental part of the Ruby on Rails framework. Of course, there is code that comes before controllers, but controller code is something most of us can control.

Once you define routes at the config/routes.rb, you can hit the server on the set route, and the corresponding controller will take care of the rest. Reading the previous sentence might give an impression that everything is as simple as that. But, often, a lot of the weight falls on the controller’s shoulders. There is the concern of authentication and authorization, then there are problems of how to fetch the needed data, as well as where and how to perform business logic.

All of these concerns and responsibilities that can occur inside the controller can lead to some anti-patterns. One of the most ‘famous’ ones is the anti-pattern of a “fat” controller.

Fat (Obese) Controllers

The problem with putting too much logic in the controller is that you are starting to violate the Single Responsibility Principle (SRP). This means that we are doing too much work inside the controller. Often, this leads to a lot of code and responsibilities piling up there. Here, ‘fat’ refers to the extensive code contained in the controller files, as well as the logic the controller supports. It is often considered an anti-pattern.

There are a lot of opinions on what a controller should do. A common ground of the responsibilities a controller should have include the following:

  • Authentication and authorization - checking whether the entity (oftentimes, a user) behind the request is who it says it is and whether it is allowed to access the resource or perform the action. Often, authentication is saved in the session or the cookie, but the controller should still check whether authentication data is still valid.
  • Data fetching - it should call the logic for finding the right data based on the parameters that came with the request. In the perfect world, it should be a call to one method that does all the work. The controller should not do the heavy work, and should delegate it further.
  • Template rendering - finally, it should return the right response by rendering the result with the proper format (HTML, JSON, etc.). Or, it should redirect to some other path or URL.

Following these ideas can save you from having too much going on inside the controller actions and controller in general. Keeping it simple at the controller level will allow you to delegate work to other areas of your application. Delegating responsibilities and testing them one by one will ensure that you are developing your app to be robust.

Sure, you can follow the above principles, but you must be eager for some examples. Let’s dive in and see what patterns we can use to relieve controllers of some weight.

Query Objects

One of the problems that happen inside controller actions is too much querying of data. If you followed our blog post on Rails Model anti-patterns and patterns, we went through a similar problem where models had too much querying logic. But, this time we’ll use a pattern called Query Object. A Query Object is a technique that isolates your complex queries into a single object.

In most cases, Query Object is a Plain Old Ruby Object that is initialized with an ActiveRecord relation. A typical Query Object might look like this:

# app/queries/all_songs_query.rb

class AllSongsQuery
  def initialize(songs = Song.all)
    @songs = songs
  end

  def call(params, songs = Song.all)
    songs.where(published: true)
         .where(artist_id: params[:artist_id])
         .order(:title)
  end
end

It is made to be used inside the controller like so:

class SongsController < ApplicationController
  def index
    @songs = AllSongsQuery.new.call(all_songs_params)
  end

  private

  def all_songs_params
    params.slice(:artist_id)
  end
end

You can also try out another approach of the query object:

# app/queries/all_songs_query.rb

class AllSongsQuery
  attr_reader :songs

  def initialize(songs = Song.all)
    @songs = songs
  end

  def call(params = {})
    scope = published(songs)
    scope = by_artist_id(scope, params[:artist_id])
    scope = order_by_title(scope)
  end

  private

  def published(scope)
    scope.where(published: true)
  end

  def by_artist_id(scope, artist_id)
    artist_id ? scope.where(artist_id: artist_id) : scope
  end

  def order_by_title(scope)
    scope.order(:title)
  end
end

The latter approach make the query object more robust by making params optional. Also, you will notice that now we can call AllSongsQuery.new.call. If you’re not a big fan of this, you can resort to class methods. If you write your query class with class methods, it will no longer be an ‘object’, but this is a matter of personal taste. For illustration purposes, let’s see how we AllSongsQuery simpler to call in the wild.

The latter approach makes the query object more robust by making params optional. Also, notice that we can now call AllSongsQuery.new.call. If you’re not a big fan of this, you can resort to class methods. If you write your query class with class methods, it will no longer be an ‘object’, but this is a matter of personal taste. For illustration purposes, let’s see how we can make AllSongsQuery simpler to call in the wild.

# app/queries/all_songs_query.rb

class AllSongsQuery
  class << self
    def call(params = {}, songs = Song.all)
      scope = published(songs)
      scope = by_artist_id(scope, params[:artist_id])
      scope = order_by_title(scope)
    end

    private

    def published(scope)
      scope.where(published: true)
    end

    def by_artist_id(scope, artist_id)
      artist_id ? scope.where(artist_id: artist_id) : scope
    end

    def order_by_title(scope)
      scope.order(:title)
    end
  end
end

Now, we can call AllSongsQuery.call and we’re done. We can pass in params with artist_id. Also, we can pass the initial scope if we need to change it for some reasons. If you really want to avoid calling of the new over a query class, try out this ‘trick’:

# app/queries/application_query.rb

class ApplicationQuery
  def self.call(*params)
    new(*params).call
  end
end

You can create the ApplicationQuery and then inherit from it in other query classes:

# app/queries/all_songs_query.rb
class AllSongsQuery < ApplicationQuery
  ...
end

You still keep the AllSongsQuery.call, but you made it more elegant.

What’s great about query objects is that you can test them in isolation and ensure that they are doing what they should do. Furthermore, you can extend these query classes and test them without worrying too much about the logic in the controller. One thing to note is that you should handle your request parameters elsewhere, and not rely on the query object to do so. What do you think, are you going to give query object a try?

Ready To Serve

OK, so we’ve handled ways to delegate the gathering and fetching of data into Query Objects. What do we do with the pilled-up logic between data gathering and the step where we render it? Good that you asked, because one of the solutions is to use what are called Services. A service is oftentimes regarded as a PORO (Plain Old Ruby Object) that performs a single (business) action. We will go ahead and explore this idea a bit below.

Imagine we have two services. One creates a receipt, the other sends a receipt to the user like so:

# app/services/create_receipt_service.rb
class CreateReceiptService
  def self.call(total, user_id)
    Receipt.create!(total: total, user_id: user_id)
  end
end

# app/services/send_receipt_service.rb
class SendReceiptService
  def self.call(user)
    receipt = user.receipt.last

    UserMailer.send_receipt(receipt).deliver_later
  end
end

Then, in our controller we would call the SendReceiptService like this:

# app/controllers/receipts_controller.rb

class ReceiptsController < ApplicationController
  def create
    receipt = CreateReceiptService.call(total: receipt_params[:total],
                                        user_id: receipt_params[:user_id])

    SendReceiptService.call(receipt)
  end
end

Now you have two services doing all the work, and the controller just calls them. You can test these separately, but the problem is, there’s no clear connection between the services. Yes, in theory, all of them perform a single business action. But, if we consider the abstraction level from the stakeholders’ perspective — their view of the action of creating a receipt involves sending an email of it. Whose level of abstraction is ‘right’™️?

To make this thought experiment a bit more complex, let’s add a requirement that the total sum on the receipt has to be calculated or fetched from somewhere during the creation of the receipt. What do we do then? Write another service to handle the summation of the total sum? The answer might be to follow the Single Responsibility Principle (SRP) and abstract things away from each other.

# app/services/create_receipt_service.rb
class CreateReceiptService
  ...
end

# app/services/send_receipt_service.rb
class SendReceiptService
  ...
end

# app/services/calculate_receipt_total_service.rb
class CalculateReceiptTotalService
  ...
end

# app/controllers/receipts_controller.rb
class ReceiptsController < ApplicationController
  def create
    total = CalculateReceiptTotalService.call(user_id: receipts_controller[:user_id])

    receipt = CreateReceiptService.call(total: total,
                                        user_id: receipt_params[:user_id])

    SendReceiptService.call(receipt)
  end
end

By following SRE, we make sure our services can be composed together into larger abstractions, like ReceiptCreation process. By creating this ‘process’ class, we can group all the actions needed to complete the process. What do you think about this idea? It might sound like a too much of abstraction at first, but it might prove beneficial if you are calling these actions all over the place.

By following SRP, we make sure that our services can be composed together into larger abstractions, like the ReceiptCreation process. By creating this ‘process’ class, we can group all the actions needed to complete the process. What do you think about this idea? It might sound like too much abstraction at first, but it might prove beneficial if you are calling these actions all over the place. If this sounds good to you, check out the Trailblazer’s Operation.

To sum up, the new CalculateReceiptTotalService service can deal with all the number crunching. Our CreateReceiptService is responsible for writing a receipt to the database. The SendReceiptService is there to dispatch emails to users about their receipts. Having these small and focused classes can make combining them in other use cases easier, thus resulting in an easier to maintain and easier to test codebase.

To sum up, the new CalculateReceiptTotalService service can deal with all the number crunching. Our CreateReceiptService is responsible for writing a receipt to the database. The SendReceiptService is there to dispatch emails to users about their receipts. Having these small and focused classes can make combining them in other use cases easier, thus resulting in an easier to maintain and easier to test codebase.

The Service Backstory

In the Ruby world, the approach of using service classes is also known as actions, operations, and similar. What these all boil down to is the Command pattern. The idea behind the Command pattern is that an object (or in our example, a class) is encapsulating all the information needed to perform a business action or trigger an event. The information that the caller of the command should know is:

  • name of the command
  • method name to call on the command object/class
  • values to be passed for the method parameters

So, in our case, the caller of a command is a controller. The approach is very similar, just the naming in Ruby is ‘Service’.

Split Up The Work

If your controllers are calling some 3rd party services and they are blocking your rendering, maybe it’s time to extract these calls and render them separately with another controller action. An example of this can be when you try to render a book’s information and fetch its rating from some other service that you can’t really influence (like Goodreads).

# app/controllers/books_controller.rb

class BooksController < ApplicationController
  def show
    @book = Book.find(params[:id])

    @rating = GoodreadsRatingService.new(book).call
  end
end

If Goodreads is down or something similar, your users are going to have to wait for the request to Goodreads servers to timeout. Or, if something is slow on their servers, the page will load slowly. You can extract the calling of the 3rd party service into another action like so:

# app/controllers/books_controller.rb

class BooksController < ApplicationController
  def show
    @book = Book.find(params[:id])
  end

  def rating
    @rating = GoodreadsRatingService.new(book).call

    render partial: 'book_rating'
  end
end

Then, you will have to call the rating path from your views, but hey, your show action doesn’t have a blocker anymore. Also, you need the ‘book_rating’ partial. To do this more easily, you can use the render_async gem. You just need to put the following statement where you render your book’s rating:

<%= render_async book_rating_path %>

Extract HTML for rendering the rating into the book_rating partial, and put:

<%= content_for :render_async %>

Inside your layout file, the gem will call book_rating_path with an AJAX request once your page loads, and when the rating is fetched, it will show it on the page. One big gain in this is that your users get to see the book page faster by loading ratings separately.

Or, if you want, you can use Turbo Frames from Basecamp. The idea is the same, but you just use the <turbo-frame> element in your markup like so:

<turbo-frame id="rating_1" src="/books/1/rating"> </turbo-frame>

Whatever option you choose, the idea is to split the heavy or flaky work from your main controller action and show the page to the user as soon as possible.

Final Thoughts

If you like the idea of keeping controllers thin and picture them as just ‘callers’ of other methods, then I believe this post brought some insight on how to keep them that way. The few patterns and anti-patterns that we mentioned here are, of course, not an exhaustive list. If you have an idea on what is better or what you prefer, please reach out on Twitter and we can discuss.

Definitely stay tuned on this series, we are going to do at least one more blog post where we sum up common Rails problems and takeaways from the series.

Until next time, cheers!


This article was originally posted on AppSignal


Nikola Đuza

Written by Nikola Đuza who helps developers improve their productivity by sharing pragmatic advice & applicable knowledge on JavaScript and Ruby. You can connect with him on Twitter.

© 2024 Nikola Đuza