Wednesday, October 5, 2022
HomeRuby On RailsRuby on Rails Controller Patterns and Anti-patterns

Ruby on Rails Controller Patterns and Anti-patterns


Welcome again to the fourth installment of the
Ruby on Rails Patterns and Anti-Patterns collection.

Beforehand, we lined patterns and anti-patterns generally in addition to in
relation to Rails Fashions and Views. On this submit, we’re going to analyze the
ultimate a part of the MVC (Mannequin-View-Controller) design sample — the Controller.
Let’s dive in and undergo the patterns and anti-patterns associated to Rails
Controllers.

At The Entrance Traces

Since Ruby on Rails is an online framework, HTTP requests are a significant a part of it.
All types of Purchasers attain out to Rails backends by way of requests and that is the place
controllers shine. Controllers are on the entrance traces of receiving and dealing with
requests. That makes them a basic a part of the Ruby on Rails framework. Of
course, there may be code that comes earlier than controllers, however controller code is
one thing most of us can management.

When you outline routes on the config/routes.rb, you possibly can hit the server on the
set route, and the corresponding controller will maintain the remaining. Studying
the earlier sentence may give an impression that every thing is so simple as
that. However, usually, loads of the burden falls on the controller’s shoulders.
There may be the priority of authentication and authorization, then there are
issues of the way to fetch the wanted knowledge, in addition to the place and the way to carry out
enterprise logic.

All of those issues and duties that may happen contained in the controller
can result in some anti-patterns. Some of the ‘well-known’ ones is the
anti-pattern of a “fats” controller.

Fats (Overweight) Controllers

The issue with placing an excessive amount of logic within the controller is that you’re
beginning to violate the Single Duty Precept (SRP). Which means that
we’re doing an excessive amount of work contained in the controller. Usually, this results in loads of
code and duties piling up there. Right here, ‘fats’ refers back to the in depth
code contained within the controller information, in addition to the logic the controller
helps. It’s usually thought-about an anti-pattern.

There are loads of opinions on what a controller ought to do. A typical floor of
the duties a controller ought to have embrace the next:

  • Authentication and authorization – checking whether or not the entity
    (oftentimes, a consumer) behind the request is who it says it’s and whether or not it
    is allowed to entry the useful resource or carry out the motion. Usually,
    authentication is saved within the session or the cookie, however the controller
    ought to nonetheless test whether or not authentication knowledge continues to be legitimate.
  • Information fetching – it ought to name the logic for locating the fitting knowledge primarily based on
    the parameters that got here with the request. Within the excellent world, it ought to be
    a name to 1 technique that does all of the work. The controller shouldn’t do the
    heavy work, and may delegate it additional.
  • Template rendering – lastly, it ought to return the fitting response by
    rendering the outcome with the right format (HTML, JSON, and many others.). Or, it ought to
    redirect to another path or URL.

Following these concepts can prevent from having an excessive amount of occurring contained in the
controller actions and controller generally. Preserving it easy on the
controller degree will help you delegate work to different areas of your
software. Delegating duties and testing them one after the other will
guarantee that you’re creating your app to be sturdy.

Positive, you possibly can observe the above rules, however you have to be longing for some
examples. Let’s dive in and see what patterns we will use to alleviate controllers
of some weight.

Question Objects

One of many issues that occur inside controller actions is an excessive amount of querying
of knowledge. When you adopted our weblog submit on
Rails Mannequin anti-patterns and patterns,
we went by means of an identical downside the place fashions had an excessive amount of querying
logic. However, this time we’ll use a sample known as Question Object. A Question Object
is a method that isolates your advanced queries right into a single object.

Normally, Question Object is a Plain Previous Ruby Object that’s initialized with
an ActiveRecord relation. A typical Question Object may seem like this:



class AllSongsQuery
  def initialize(songs = Music.all)
    @songs = songs
  finish

  def name(params, songs = Music.all)
    songs.the place(revealed: true)
         .the place(artist_id: params[:artist_id])
         .order(:title)
  finish
finish

It’s made for use contained in the controller like so:

class SongsController < ApplicationController
  def index
    @songs = AllSongsQuery.new.name(all_songs_params)
  finish

  personal

  def all_songs_params
    params.slice(:artist_id)
  finish
finish

You can too check out one other strategy of the question object:



class AllSongsQuery
  attr_reader :songs

  def initialize(songs = Music.all)
    @songs = songs
  finish

  def name(params = {})
    scope = revealed(songs)
    scope = by_artist_id(scope, params[:artist_id])
    scope = order_by_title(scope)
  finish

  personal

  def revealed(scope)
    scope.the place(revealed: true)
  finish

  def by_artist_id(scope, artist_id)
    artist_id ? scope.the place(artist_id: artist_id) : scope
  finish

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

The latter strategy make the question object extra sturdy by making params
non-compulsory. Additionally, you’ll discover that now we will name AllSongsQuery.new.name.
When you’re not a giant fan of this, you possibly can resort to class strategies. When you write
your question class with class strategies, it is going to not be an ‘object’, however this
is a matter of private style. For illustration functions, let’s see how we AllSongsQuery less complicated to name within the wild.

The latter strategy makes the question object extra sturdy by making params
non-compulsory. Additionally, discover that we will now name AllSongsQuery.new.name. When you’re
not a giant fan of this, you possibly can resort to class strategies. When you write your question
class with class strategies, it is going to not be an ‘object’, however it is a
matter of private style. For illustration functions, let’s see how we will
make AllSongsQuery less complicated to name within the wild.



class AllSongsQuery
  class << self
    def name(params = {}, songs = Music.all)
      scope = revealed(songs)
      scope = by_artist_id(scope, params[:artist_id])
      scope = order_by_title(scope)
    finish

    personal

    def revealed(scope)
      scope.the place(revealed: true)
    finish

    def by_artist_id(scope, artist_id)
      artist_id ? scope.the place(artist_id: artist_id) : scope
    finish

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

Now, we will name AllSongsQuery.name and we’re completed. We are able to move in params
with artist_id. Additionally, we will move the preliminary scope if we have to change it
for some causes. When you actually need to keep away from calling of the new over a question class, check out this ‘trick’:



class ApplicationQuery
  def self.name(*params)
    new(*params).name
  finish
finish

You possibly can create the ApplicationQuery after which inherit from it in different question
courses:


class AllSongsQuery < ApplicationQuery
  ...
finish

You continue to maintain the AllSongsQuery.name, however you made it extra elegant.

What’s nice about question objects is that you may take a look at them in isolation and
make sure that they’re doing what they need to do. Moreover, you possibly can prolong
these question courses and take a look at them with out worrying an excessive amount of concerning the logic in
the controller. One factor to notice is that you need to deal with your request
parameters elsewhere, and never depend on the question object to take action. What do you
assume, are you going to offer question object a strive?

Prepared To Serve

OK, so we’ve dealt with methods to delegate the gathering and fetching of knowledge into
Question Objects. What can we do with the pilled-up logic between knowledge gathering
and the step the place we render it? Good that you just requested, as a result of one of many
options is to make use of what are known as Companies. A service is oftentimes regarded
as a PORO (Plain Previous Ruby Object) that performs a single (enterprise) motion. We
will go forward and discover this concept a bit beneath.

Think about now we have two providers. One creates a receipt, the opposite sends a receipt
to the consumer like so:


class CreateReceiptService
  def self.name(whole, user_id)
    Receipt.create!(whole: whole, user_id: user_id)
  finish
finish


class SendReceiptService
  def self.name(consumer)
    receipt = consumer.receipt.final

    UserMailer.send_receipt(receipt).deliver_later
  finish
finish

Then, in our controller we might name the SendReceiptService like this:



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

    SendReceiptService.name(receipt)
  finish
finish

Now you could have two providers doing all of the work, and the controller simply calls
them. You possibly can take a look at these individually, however the issue is, there’s no clear
connection between the providers. Sure, in principle, all of them carry out a single
enterprise motion. However, if we contemplate the abstraction degree from the
stakeholders’ perspective — their view of the motion of making a receipt
includes sending an e mail of it. Whose degree of abstraction is ‘proper’™️?

To make this thought experiment a bit extra advanced, let’s add a requirement
that the entire sum on the receipt must be calculated or fetched from
someplace in the course of the creation of the receipt. What can we do then? Write one other
service to deal with the summation of the entire sum? The reply could be to observe
the Single Duty Precept (SRP) and summary issues away from every
different.


class CreateReceiptService
  ...
finish


class SendReceiptService
  ...
finish


class CalculateReceiptTotalService
  ...
finish


class ReceiptsController < ApplicationController
  def create
    whole = CalculateReceiptTotalService.name(user_id: receipts_controller[:user_id])

    receipt = CreateReceiptService.name(whole: whole,
                                        user_id: receipt_params[:user_id])

    SendReceiptService.name(receipt)
  finish
finish

By following SRE, we make sure that our providers might be composed collectively into
bigger abstractions, like ReceiptCreation course of. By creating this ‘course of’
class, we will group all of the actions wanted to finish the method. What do you
take into consideration this concept? It’d sound like a an excessive amount of of abstraction at first,
nevertheless it may show helpful in case you are calling these actions throughout
the place.

By following SRP, we make it possible for our providers might be composed collectively into
bigger abstractions, just like the ReceiptCreation course of. By creating this
‘course of’ class, we will group all of the actions wanted to finish the method.
What do you consider this concept? It’d sound like an excessive amount of abstraction at
first, nevertheless it may show helpful in case you are calling these actions throughout
the place. If this sounds good to you, try the Trailblazer’s
Operation
.

To sum up, the brand new CalculateReceiptTotalService service can take care of all of the
quantity crunching. Our CreateReceiptService is liable for writing a
receipt to the database. The SendReceiptService is there to dispatch emails
to customers about their receipts. Having these small and targeted courses could make
combining them in different use circumstances simpler, thus leading to a better to
preserve and simpler to check codebase.

To sum up, the brand new CalculateReceiptTotalService service can take care of all of the
quantity crunching. Our CreateReceiptService is liable for writing a receipt
to the database. The SendReceiptService is there to dispatch emails to customers
about their receipts. Having these small and targeted courses could make combining
them in different use circumstances simpler, thus leading to a better to keep up and
simpler to check codebase.

The Service Backstory

Within the Ruby world, the strategy of utilizing service courses is also referred to as
actions, operations, and comparable. What these all boil right down to is the Command sample.
The thought behind the Command sample is that an object (or in our
instance, a category) is encapsulating all the knowledge wanted to carry out a
enterprise motion or set off an occasion. The data that the caller of the
command ought to know is:

  • identify of the command
  • technique identify to name on the command object/class
  • values to be handed for the tactic parameters

So, in our case, the caller of a command is a controller. The strategy
may be very comparable, simply the naming in Ruby is ‘Service’.

Cut up Up The Work

In case your controllers are calling some third occasion providers and they’re blocking
your rendering, possibly it’s time to extract these calls and render them
individually with one other controller motion. An instance of this may be if you
attempt to render a e book’s info and fetch its ranking from another service
that you may’t actually affect (like Goodreads).



class BooksController < ApplicationController
  def present
    @e book = E-book.discover(params[:id])

    @ranking = GoodreadsRatingService.new(e book).name
  finish
finish

If Goodreads is down or one thing comparable, your customers are going to have to attend
for the request to Goodreads servers to timeout. Or, if one thing is sluggish on
their servers, the web page will load slowly. You possibly can extract the calling of the
third occasion service into one other motion like so:



class BooksController < ApplicationController
  def present
    @e book = E-book.discover(params[:id])
  finish

  def ranking
    @ranking = GoodreadsRatingService.new(e book).name

    render partial: 'book_rating'
  finish
finish

Then, you’ll have to name the ranking path out of your views, however hey, your present
motion doesn’t have a blocker anymore. Additionally, you want the ‘book_rating’
partial. To do that extra simply, you need to use the render_async gem.
You simply have to put the next assertion the place you render your e book’s ranking:

<%= render_async book_rating_path %>

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

<%= content_for :render_async %>

Inside your format file, the gem will name book_rating_path with an AJAX
request as soon as your web page hundreds, and when the ranking is fetched, it is going to present it
on the web page. One large achieve in that is that your customers get to see the e book web page
sooner by loading rankings individually.

Or, if you need, you need to use Turbo Frames
from Basecamp. The thought is similar, however you simply use the <turbo-frame>
factor in your markup like so:

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

No matter possibility you select, the concept is to separate the heavy or flaky work from
your foremost controller motion and present the web page to the consumer as quickly as attainable.

Last Ideas

When you like the concept of conserving controllers skinny and film them as simply
‘callers’ of different strategies, then I imagine this submit introduced some perception on
the way to maintain them that manner. The few patterns and anti-patterns that we talked about
listed here are, in fact, not an exhaustive record. When you’ve got an thought on what’s
higher or what you like, please attain out on Twitter and we will talk about.

Positively keep tuned on this collection, we’re going to do at the very least yet another weblog
submit the place we sum up frequent Rails issues and takeaways from the collection.

Till subsequent time, cheers!

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments