Design Patterns Used in Ruby on Rails

10 Popular Design Patterns for Ruby on Rails

 on June 17, 2021

10 Popular Design Patterns for Ruby on Rails

Design patterns are some of the best things that you can do for your codebase and your future developers. Rails already implements the Model-View-Controller (MVC) design pattern to reduce clutter and organize code better. Such rails design patterns help to keep code smell away and achieve the ever-active goal of "Fat Model, Skinny Controller."

In this article, we will take a look at the concept of design patterns in detail. We will also go through how and why they make our lives easier and when it is not good to use them. Finally, we will discuss ten popular design patterns for Ruby on Rails and examine an example of each.

Without further ado, let's dive right in!

Feel free to use these links to navigate the guide:

  • What is a Design Pattern?
    • Why Use Design Patterns?
    • Potential Cons of Using Design Patterns
  • Popular Rails Design Patterns
    • Builder
    • Decorator
    • Form
    • Interactor
    • Observer
    • Policy
    • Presenter
    • Query
    • Service
    • Value

What is a Design Pattern?

Design patterns are some of the best practices that you should follow while composing your application's code architecture to enhance readability and be able to maintain it better in the future. Design patterns also help reduce resource leaks and common mistakes since you must follow a pre-tested style of designing your codebase.

It is essential to understand that design patterns are not concrete—they are merely concepts and recommended practices to follow. This article, or any guide on design patterns for that sake, will not give you working code that you can plug into your application right away. Instead, you will learn the design principles behind each pattern and how it solves a particular problem.

The intent behind design patterns is to channel your thinking through a set of tried and tested guidelines. These methodologies are bound to solve your problems and make your code better, but they can get inflexible at times.

Now that you understand what design patterns are, let's look at some of the reasons you should use design patterns in your code.

Why Use Design Patterns?

Design patterns offer several benefits over the traditional way of coding from scratch. Here are some of the top reasons why you should consider using design patterns in your application:

  • Routine : Using design patterns helps you to follow pre-built practices and not waste time reinventing the wheel.
  • Improved Object-Oriented Skills : With the use of design patterns over time, you strengthen your understanding of object-oriented programming.
  • Understanding of Standards Used Across Libraries : Most libraries and languages use design patterns in their APIs, so the frequent use of design patterns will help you understand the implementation of most standard libraries and languages.
  • Sharing Code Vocabulary With Team : If you and your team follow standard design patterns in a project, it will be easy for everybody to understand their code.
  • Innovating Your Thinking : When you learn the building concepts behind design patterns, you open new avenues of thinking for new architectures for your codebase.

Potential Cons of Using Design Patterns

While design patterns seem like the most significant things in programming, they come at a cost. Here are a couple of potential downsides to using design patterns:

  • Requires Knowledge : Using design patterns in a group project requires knowledge of the required patterns from everybody. This adds to the workload of the team.
  • Can Limit Thinking : Design patterns are all about limiting yourself to a set of predefined ways of thinking while designing code. This can, at times, limit the overall creativity of a developer in innovating for specific, challenging problems.
  • Overkill For Small Use Case : Design patterns work the best for projects where developers must write a lot of code. In smaller projects, following design patterns can add unnecessary boilerplate to the codebase.

Popular Rails Design Patterns

Here are ten popular rails design patterns to help you make your next rails project clean and crash-free.

Builder

The Builder pattern makes the construction of complex objects gradual and straightforward. It is also known as Adapter, and its primary purpose is to simplify the object instantiation process.

Problem

Imagine if you were building a highly customized car. You would need multiple stages of development—internal structure or chassis, engine, and related machinery, outer body structure, designing and decals, and other comfort-related installations like dashboard, seats, etc. If you were to represent this process via classes, you would have one colossal class constructor that would take in all of these preferences as arguments. But that can get too cluttered for creating a basic, economy four-wheeler. This is where the concept of builders kicks in.

Solution

Like the way a car goes through a series of steps in an assembly, the builder pattern splits the construction of any object into a series of small and gradual steps. For the car example, there would be a ChassisBuilder, PerformanceBuilder, BodyBuilder, InstallationBuilder, and more, at each step of the car assembly process. The output of each of these builder interfaces would be an input to the next. This way, you don't need to maintain one huge constructor for your Car class that can get prone to errors.

When To Use

The Builder pattern is helpful in the following situations:

  • When you need many permutations of the existing features in a class while creating new objects.
  • When you are looking to define an assembly process for creating objects, rather than relying on constructors alone.

Example

Let's say your application requires users to upload a document. It can be in any format, including PDF, JPEG, PNG, etc. Each of these file types requires a unique way of handling a particular function, say conversion to a string. Creating one large class that covers them all can make the code cluttered and hard to understand.

The builder pattern can help create a modularised build process for the correct type of parser based on the file extension encountered.

This is how your parser classes would look:

          class BaseParser     def initialize(path)         @path = path      end end  class PdfFileParser < BaseParser     def toString         # return a string     end end  class JpegFileParser < BaseParser     def toString          # return a string     end end                  


And here's how you can create a builder for the final parser object:

          class ParserBuilder     def self.build(path)         case File.extname(path)             when '.pdf' then PdfFileParser.new(path)             when '.jpeg' then JpegFileParser.new(path)             else                 raise(UnknownFileFormat)             end     end end        

Finally, here's how you can use this builder:

          path = '../../something.ext' # input from user imageParser = ParserBuilder.build(path) storage.save(imageParser.toString())        

Decorator

The Decorator pattern adds new functionality to an existing object by wrapping it with additional objects containing the new features. This helps to modify an instance of a class for extra features and not define new static subclasses in the code.

Problem

Imagine you are trying to build cars using programming, and you don't want to go into the hassle of creating extensive builders for all types of possible cars. Instead, you want to begin by creating an introductory car class with the most standard features and preferences. Now, if you need to customize, say, the car's roof to be collapsible, you would conventionally need to extend the base car class to a subclass and provide it with a collapsible roof attribute. If you again need to change a feature, say, add carbon hoods to the car, you need to extend the base class again. It might seem evident now that you must create a new, static class in your codebase for each customization, but this can very quickly bloat the code.

Solution

Decorators solve this issue by providing a set of interfaces that can take in a plain, base class object and return a decorated version of that object, meaning wrap it around with the feature requested. This way, your base class stays clean and straightforward, and your decorators are highly modularised, covering only the features needed.

When To Use

The Decorator pattern is helpful in the following situations:

  • When you want to make modifications to existing objects of a class rather than a class itself.
  • When you want each additional feature to be independent of others without following a fixed sequence to modify the object.

Example

Here's how you can create a decorator for the above problem:

          class Car     def roof         "fixed"     end end  class ConvertibleDecorator < SimpleDelegator     def roof         "collapsible"     end      def self.decorate(car)         new(car)     end      private      def model         __getobj__     end end        


And this is how you can use it:

          basicCar1 = Car.new() basicCar2 = Car.new()  basicCar1 = ConvertibleDecorator.decorate(basicCar1)  puts(basicCar1.roof) # => collapsible puts(basicCar2.roof) # => fixed        

Form

The form design pattern encapsulates the logic related to data validation and persistence into a single unit. As a result, it can often separate the logic from the views and make the form unit reusable throughout the codebase.

Problem

It seems easy to write the validation logic for a view with it or its model in most cases. You know where to look for the code, and if it's short, it's even better. However, this poses a severe threat to the overall quality of your codebase. You can not reuse logic written in views or models, and maintaining multiple copies of similar code can be problematic.

Say you build an application that stores and displays posts made by the users, and each of the posts consists of the author's name, author's email, post content, and a timestamp. You can choose to define its validation rules inside the Post model itself. While it would serve its purpose here, you will not be able to re-use the logic anywhere else. Say you were to design a comments system later in which each comment would use the same attributes as above (author, email, content, timestamp), you would have to rewrite the same validation rules for the new model. With the form pattern, you can save these efforts.

Solution

Using the form design pattern, you can isolate the validation and other relevant logic from your models and views. You can wrap it together in a Form object and reuse the logic wherever needed. This will reduce multiple copies of the same code and make it easy to debug your application.

When To Use

The Form pattern is preferable to use when:

  • You are looking to reuse form validation rules across components.
  • You wish to reduce code bloat inside models and controllers.
  • You are trying to modularise your codebase and maintain each segment independently.

Example

Take a look at the following code:

          class PostsController < ApplicationController     def create         @post = Post.new(post_params)          if @post.save             render json: @post         else             render json: @post.error, status :unprocessable_entity         end     end      private      def post_params         params             .require(:post)             .permit(:email, :author, :content, :timestamp)         end     end  class Post < ActiveRecord::Base     EMAIL_REGEX = /@/      validates :author, presence: true     validates :email, presence: true, format: EMAIL_REGEX     validates :content, presence: true     validates :timestamp, presence: true end        

In the above piece of code, the Post model contains the validation logic inside of it. If you want to create another model called "Comment," you would have to rewrite the same logic again. Here's how you can isolate the logic from the model using the form design pattern:

          class PostForm     EMAIL_REGEX = /@/      include ActiveModel::Model     include Virtus.model      attribute :id, Integer     attribute :author, String     attribute :email, String     attribute :content, String     attribute :timestamp, String      validates :author, presence: true     validates :email, presence: true, format: EMAIL_REGEX     validates :content, presence: true     validates :timestamp, presence: true      attr_reader :record      def persist         @record = id ? Post.find(id) : Post.new          if valid?             @record.attributes = attributes.except(:id)             @record.save!             true         else             false         end     end end        


Once the logic has been written in PostForm, you can use it inside a Controller like this:

          class PostsController < ApplicationController     def create         @post = UserPost.new(post_params)              if @post.persist             render json: @post.record         else             render json: @form.errors, status: :unprocessable_entity         end     end      private      def post_params         params             .require(:post)             .permit(:author, :email, :content, :timestamp)     end end                  

Interactor

You can use the interactor design pattern to split a large task into a series of small, inter-dependent steps. If any of the steps fail, the flow stops, and a relevant message appears about why the execution failed.

Problem

In many real-life scenarios, such as making a purchase or completing a bank transaction, multiple steps are involved. Each of these steps usually consists of entire tasks in themselves. To complete a sale, you need to create the sale record, find and update the stock for the product sold, process and ship the product to the shipping division, and finally send a confirmation email to the user. You could choose to do this by writing one large method that completes these steps one by one. But there would be several issues with the method:

  • Cluttered : One method with a large amount of code will be challenging to understand and maintain.
  • Fragile : Since there is no order involved, you can easily misplace a single line of code and watch your application crumble to pieces.
  • Inflexible : If you need to add a small change like validation in one of the intermediate steps, you would have to refactor the complete method in some cases.

Solution

To solve all of the problems mentioned above, you can use the interactor design pattern to modularise each intermediate step and isolate their code. Each of these modules is called interactors. If any of the interactors fails, those after it will not execute, similar to how your flow would have worked before.

When To Use

The Iterator pattern is most useful in the following situations:

  • When you are trying to break down an extensive process into smaller steps.
  • When you need the flexibility of making frequent changes to the substeps of an extensive process.
  • When you are looking to use external APIs in a small part of a more significant task

Example

This is how you can create a sale process using the interactor design pattern:

          class ManageStocks     include Interactor          def call         # retrieve and update stocks here     end end  class DispatchShipment     include Interactor          def call         # order the dispatch of the goods here     end end  class ScheduleMail     include Interactor      def call         # send an email to the recipient here     end end  class MakeSale     include Interactor::Organizer      organize ManageStocks, DispatchShipment, ScheduleMail end                  


Here's how you can perform a transaction:

          result = MakeSale.call(     recipient: user, products: products )  puts outcome.success? puts outcome.message        


Observer

The Observer pattern notifies other interested objects if any events occur with the object in observation. The observed object maintains a list of its observers and sends them an update whenever its state changes.

Problem

Let's say you are building an online store that offers multiple types of products. You already have a database of your customers as well as your products. It's possible that some of your customers are interested in a particular type of product, say laptops. They would want to know when your store has new laptops to offer. Now, they could try checking your store every once in a while for new laptops, but that can get tiresome. Also, you could consider sending out emails to every customer whenever any new products arrive. But that would also send emails about new laptops to those who aren't interested in laptops. This creates a situation in which either party has to overspend resources to maintain a good customer experience and business revenue.

Solution

The easiest solution to the above problem is to maintain a preference list for every customer. Each customer can choose the products they want notifications about, and when a new product arrives, the store can check the list and mail the interested customers accordingly. This is what the Observer pattern does. The products in the above example are known as publishers or the objects that notify other objects. The customers from the above example are subscribers or the objects that receive notifications of changes. The publisher also contains measures to add or remove subscribers , which was done by signing up or out of the preferences list in our above example.

When To Use

The Observer pattern is beneficial in the following situations:

  • When you are making multiple view changes manually via code.
  • When the state of more than one object directly depends on the state of a specific object.
  • When there are multiple views whose state depends on the state of a particular object.

Example

Let's build the product and customer example that we talked about earlier. First of all, this is how a basic implementation of a Product would look:

          require 'observer'  class Product   include Observable    attr_reader :name, :stock    def initialize(name = "", stock = 0)     @name, @stock = name, stock     add_observer(Notifier.new)   end    def update_stock(stock)     @stock = stock     changed     notify_observers(self, stock)   end end                  

Next, you need a subscriber class that will receive updates on the stock changes:

          class Notifier   def update(product, stock)     puts "The #{product.name} is in stock" if stock > 0     puts "The #{product.name} is sold out!" if stock == 0   end end                  


Finally, this is how you can use them together:

          product = Product.new("AB Laptop")  product.update_stock(7) # => The AB Laptop is in stock  product.update_stock(3) # => The AB Laptop is in stock  product.update_stock(0) # => The AB Laptop is sold out!  product.update_stock(2) # => The AB Laptop is in stock  product.update_stock(0) # => The AB Laptop is sold out!        


Policy

The policy design pattern isolates the validation logic from models. The model is unaware of the validation implementation in the policy, and it receives a boolean value only.

Problem

In many cases, classes contain multiple validation checks on their attributes. This occurs inside the class itself, which becomes a severe code smell issue when the number of classes and validation checks increases. In most real-life cases, multiple classes use a common set of validation checks on their attributes; hence maintaining a separate entity for validation makes sense.

Solution

The solution to the above problem is to create a Policy class that contains the validation logic for each model. You can use this class to retrieve true or false results where validation is required. Once again, making changes to how the validation occurs will not affect the model's logic, and there will be fewer errors and less clutter.

When To Use

The Policy pattern is helpful in the following situations:

  • When you have multiple checks in your code and all of them return a boolean value.
  • When your checks involve more than one model or API.

Example

Here is how you can define a Policy class:

          class PostPolicy     def initialize(post)        @post = post     end      private     attr_reader :post      def hasEmail?         post.email?     end      def hasAuthor?         post.author?     end end                  


The above policy implements checks for email and author. You can use it in the following way:

          class PostHandler     def uploadPost(post)     # method to upload a post to database         policy = PostPolicy.new(post)          if (policy.hasEmail && policy.hasAuthor)             # save to database here         else             puts "error occurred"         end     end end                  

Presenter

The Presenter design pattern aims to isolate view rendering logic from the controller and model and encapsulate it into a reusable component. It makes the controller and models leaner and enhances code readability.

Problem

While programming without a proper plan, developers tend to overload their views with rendering logic, such as cleaning or formatting the data received from external APIs or conditionally rendering views based on logic defined inside controllers. While this seems like a nice little shortcut in smaller applications, this can contribute to a big code smell in growing applications. The models and controllers can get bloated with unnecessary rendering logic, and maintenance of their code can become cumbersome.

Solution

The solution to the above problem is to isolate the view rendering logic from the models, classes, and HTML views into independent modules/classes called Presenters. These Presenters will hold the logic for cleaning, formatting, and validating data and will present the view only with the final information that it can directly display. This enhances the modularity of code and makes the models and controllers leaner.

When To Use

The Presenter is pattern is suitable for use in the following conditions:

  • When you are trying to build an extensive application that has multiple models and controllers.
  • When the same model is used at multiple places, requiring the same rendering logic numerous times.

Example

Before understanding the impact of the Presenter pattern, let's take a look at an example that does not use the pattern:

          <% #/app/views/posts/view.html.erb %> <% content_for :header do %>  <title>      <%= @post.title %>  </title>  <meta name='description' content="<%= @post.description_for_head %>">   <meta property="og:type" content="post">   <meta property="og:title" content="<%= @post.title %>">   <% if @post.description_for_head %>     <meta property="og:description" content="<%= @post.description_for_head %>">   <% end %>   <% if @post.image %>      <meta property="og:image" content="<%= "#{request.protocol}#{request.host_with_port}#{@post.image}" %>">   <% end %> <% end %>  <% if @post.image %>  <%= image_tag @post.image.url %> <% else %>  <%= image_tag 'no-image.png'%> <% end %>  <h1>  <%= @post.title %> </h1> <p>  <%= @post.content %> </p>  <% if @post.author %> <p>  <%= "#{@post.author.first_name} #{@post.author.last_name}" %> </p> <%end%>  <p>  <%= t('date') %>  <%= @post.timestamp.strftime("%B %e, %Y")%> </p>                  

As you can see, the view appears very cluttered since the conditional validation and other checks have been implemented directly in the view. Here's how it would look if you used a presenter object to render the views:

          <%# /app/views/posts/view.html.erb %> <% presenter @post do |post_presenter| %>  <% content_for :header do %>    <%= post_presenter.meta_title %>    <%= post_presenter.meta_description %>    <%= post_presenter.og_type %>    <%= post_presenter.og_title %>    <%= post_presenter.og_description %>    <%= post_presenter.og_image %>  <% end %>  <%= post_presenter.image%>  <h1> <%= post_presenter.title %> </h1>  <p>  <%= post_presenter.content %> </p>  <%= post_presenter.author_name %>  <p> <%= post_presenter.timestamp %></p> <% end %>                  

And this is how your PostPresenter would look:

          class PostPresenter   def initialize(post)     @post = post   end    def image     @post.image? ? @post.image.url : 'no-image.png'   end      # similary define other methods with their logic  end                  

Query

The Query design pattern is used in Rails to isolate querying logic from Controllers. It helps to create reusable classes you can invoke in more than one place.

Problem

While writing the code for querying data in an application, developers often write down small database queries inside the controller. This seems like an easy alternative as queries are sometimes just one to two lines long, but in this process, the same short query gets written inside multiple controllers, as and when needed. This poses difficulty in maintaining the code, as a slight change in the query structure needs to be done manually across all its copies.

Solution

The solution to the above problem is to isolate the querying logic into a separate class of itself. You can invoke this class to query and provide data to the controllers. Apart from maintaining a single copy for a query, it also separates querying logic from controllers. In this way, the controllers are only concerned about running queries and getting results; the internal logic of a query is irrelevant to controllers and other classes.

When To Use

The Query pattern helps in the following situations:

  • When there is a considerable number of queries taking place in your application.
  • When queries are being re-used at multiple places.

Example

Here's a basic implementation of a Query object for isolating the logic of fetching the most popular Posts from the database:

          class PopularPostsQuery   def call(relation)     relation       .where(type: :post)       .where('view_count > ?', 100)   end end  class PostsController < ApplicationController   def index     relation = Post.accessible_by(current_ability)     @popular_posts = PopularPostsQuery.new.call(relation)   end end                  

Service

The service design pattern breaks down a program into its functional constituents. Each service object completes one task, such as running payment for a customer, completing a bank transaction, etc.

Problem

Usually, developers tend to write business logic for a task in one place. Say you are trying to build an eCommerce application. You need to define logic for checking out a cart once a user is ready. If you have initially planned only one point in your application for checking out, you will want to write the checkout logic around that point only. Later, if you try to create a quick check-out button somewhere else in your app, you will have to rewrite the complete logic at the new place. This will add to the code redundancy and reduce the maintainability of the codebase.

Solution

If you isolate the check-out logic from the above problem into a separate class of its own, you can easily invoke that class wherever you need to process a checkout request. Once again, this isolates logic from views, which makes your application more modular. If your business logic involves external APIs, using a service keeps your main code clean, while you can easily integrate and replace APIs in your service classes.

When To Use

The Service pattern is useful when:

  • You have to carry out sensitive tasks, and you wish to isolate the logic from the main code, like running a monetary transaction or registering a sale.
  • You are building your transactions to operate in multiple places in your codebase.

Example

Here's an example of a basic file downloader service that can hit any URL and display the data received in the form of a string.

          require "down"  class FileDownloaderService   def self.call(url)     tempfile = Down.download(url)     tempfile.read.to_s   end end  puts FileDownloaderService.call("https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?index=3") # Output => #[ # { #   "fact": "Apple and pear seeds contain arsenic, which may be deadly to dogs." # } #]                  

The above example shows how you can use an external API in the Service class to complete your operations, while the calling environment has no clue about how the data is retrieved.

Value

The Value design pattern isolates static, constant values from the active code of an application. You use a separate class or module for storing constant values at one place and access them wherever and whenever needed.

Problem

More often than not, a program's logic requires a range of constants to function correctly. Say you are building an application that converts temperature from one unit to another. For this, you would need to store the formula for each conversion. While you could keep this either in the controller or in the model, you would only be adding to their code weight and making the formulas inaccessible from other segments of your application.

Solution

The solution to the above problem is to define a class or a module to store the conversion formulae and use them wherever needed. Note that this is different from a Service or a Query as it only keeps constant or static data. You should not define any methods that depend upon external factors such as APIs in a Value module. It should either contain pure values or basic operations.

When To Use

The Value pattern is helpful in the following situations:

  • When your code involves a large number of constants or static operations.
  • When you want to reuse a common set of constants at multiple places in your code.
  • When you want to use different sets of constants for various flavors of your app.

Example

To understand the use of the Value pattern, first, let's take a look at the following piece of code:

          class EmailHandler     def initialize(emails)         @emails = emails     end          def get_data         emails.map { |email| {                 name: email.match(/([^@]*)/).to_s,                 domain: email.split("@").last             }         }     end          private     attr_reader :emails end  puts EmailHandler.new(["abc@xyz.com", "abd@xyx.cd"]).get_data # => {:name=>"abc", :domain=>"xyz.com"} # => {:name=>"abd", :domain=>"xyx.cd"}                  

The above code works, but as you can see, the operation to extract data from the email addresses is inside the handler class. This is how you can simplify it by using the Value design pattern:

          class EmailValues     def initialize(email)         @email = email     end          def name         email.match(/([^@]*)/).to_s     end          def domain         email.split("@").last     end          def to_h         { name: name, domain: domain }     end          private     attr_reader :email end                  

And use the Value object in the handler class:

          class EmailHandler     def initialize(emails)         @emails = emails     end          def get_data         emails.map { |email| EmailValues.new(email).to_h }     end          private     attr_reader :emails end  puts EmailHandler.new(["abc@xyz.com", "abd@xyx.cd"]).get_data # => {:name=>"abc", :domain=>"xyz.com"} # => {:name=>"abd", :domain=>"xyx.cd"}                  

Summary

Design patterns are some of the best things that you can do for your codebase and your future developers, helping to keep code smell away and achieve the ever-active goal of "Fat Model, Skinny Controller."

We took a look at the concept of design patterns in detail, went through how and why they make our lives easier, and discussed 10 design patterns and their uses. For more in-depth content around web development and a reliable tool for optimizing your application's performance, navigate our blog and feel free to explore Scout APM with a free 14-day trial!

Design Patterns Used in Ruby on Rails

Source: https://scoutapm.com/blog/rails-design-patterns

0 Response to "Design Patterns Used in Ruby on Rails"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel