The main principle of software development is to keep the code DRY (Don’t Repeat Yourself) to reduce the reduction in the code. So, Ruby On Rails follows some design patterns to achieve the DRY principle. Though we can use models, helpers, and concerns to make the code dry, if the code complexity is higher or we are using an external API, in that case, we use Design Patterns in Ruby on Rails.



What are Design Patterns?

Design patterns can be said as best practices that must be followed to avoid code complexity and maintain code readability. Implementation of such design patterns also lessens the efforts of fellow developers of understanding the business logic.

Design patterns are concepts and best practices to reduce the complexity, they are concrete code blocks. The intention behind the design patterns is to channel the business logic and algorithm of the problem.

Here, in today’s blog, we will be discussing Rails design patterns. You might have heard of design patterns in Ruby on Rails. Moving on to our first section, let’s understand why should we use design patterns in the first place.

Why use Design Patterns?

Before learning about rails design patterns and diving deeper into that; let’s go through a few reasons why use design patterns in ruby.

  • Following Routine and pre-built best practices.
  • Lesser time would be wasted in understanding the business logic
  • It will improve Object-oriented concepts
  • The frequent use of design patterns in rails will surely help you understand the standards of libraries.
  • Learning and exploring new practices in order to maintain code readability

Okay, so this was an overview of ruby on rails design patterns. Let’s move towards the next section and see the top 10 design patterns in rails.

10 Best Design Patterns in Ruby on Rails

Here are the top 10 rails design patterns.

1. Service Objects
2. View Objects (Presenter)
3. Query Objects
4. Decorators
5. Form Objects
6. Value Objects
7. Policy Objects
8. Builder
9. Interactor
10. Observer

Let’s discuss all the ruby design patterns one by one with examples.

1. Service Objects

What is a Service Object?

A Service Object is PORO – Plain Old Ruby Object, which is meant to encapsulate business logic and complex calculations into manageable classes and methods. This is one of the most used design patterns in ruby on rails.

When to use service objects?

  • When we need to perform complex calculations or business logic, e.g., if we need to calculate employees’ salaries based on their attendance.
  • When we need to implement any other API, we need to implement a payment gateway such as Stripe.
  • When we want to import CSV that contains the bulk of data. 4. When we need to clear garbage/unused/old data from the database efficiently, it won’t affect the existing data.

Implementing Design Pattern in your RoR project is not challenging anymore
Get in touch with us to hire ruby on rails developer and enhance the performance of your existing RoR application.

In this example, we perform stripe integration with the help of Service Object. Here the stripe service will create stripe customers based on their email address and token.

Look at PaymentsController; here, you can see that the controller is too skinny. The payment service has solved the problem of too much code inside the controller and made the controller skinny and readable.

Copy Text
class PaymentsController < ApplicationController 
def create
PaymentService.new(params).call 
redirect_to payments_path 
rescue Stripe: : CardError => e 
flash[:error] = e.message
redirect_to new_Payment_path 
end 
end

Now create payment_service.rb inside your_project/app/services folder.

Copy Text
class Payment Service 
    def initialize(options = {}) 
      options.each pair do Jkey, value
        instance_variable_set("@#{key}", value) 
      end 
    end
    
    def call
      Stripe: : Charge.create(charge_attributes),
   end

   private

   attr_reader : email, :source, :amount, :description

   def amount
     @amount. to_i * 100 
   end

   def customer
     @customer ||= Stripe::Customer.create(customer_attributes),
   
   def customer_attributes
   {
    email: email, 
    source: source
    }  
   end

    def charge_attributes
    {
     customer: customer.id, 
     amount: amount, 
     description: description
    }
    end
end

2. View Objects (Presenter)

View Objects allow us to encapsulate all view-related logic and keep both models and views neat. View objects are the kind of rails patterns that are easy to test as they are just classes.

To solve the calculation logic problem, we can use rails helper, but if the complexity of the code is high, in that case, we should use the Presenter.

Have a look at the below code of the view page.

Copy Text
<p> 
  User Full Name: <%= "#{user.first_name} #{user. last_name}" %>       
  <%= link_to "View More Detail", user, class: "W-75 p-3      text-#{user.active? ? "orange" : "
green"} border-#{user.active? ? "orange" : "green"}" %>
</p>

Here we can see that we concatenate the user’s first_name and last_name on the view, which is not a good practice. Hence to solve this problem, we should use the presenter.

Let’s create a presenter class to solve this.

Copy Text
class UserPresenter 
  def initialize(user)
    @user = user
  end

   def full_name
     "#{@user.first_name} #{@user. last_name}", 
   end

   def css_color
    @user.active?? "orange" : "green", 
   end
end

Save it under app/presenters/user_presenter.rb and create the presenter’s folder if you do not have it. Now let’s change the view to make it more readable without any calculations.

Copy Text
<% presenter = UserPresenter.new(user) %> 
User Full Name: <%= presenter.full_name %> 
<%= link_to "View More Detail", user, class: "W-75 p-3   text-#{presenter.css_color} border-#{presenter.css_color}" %> 

3. Query Object

Query Object is a design pattern in rails that lets us fetch query logic from Controllers and Models into reusable classes.

Let’s have a look at the below example.

We want to request a list of posts with the type “video” that has a view count greater than 1000 and that the current user can access.

Copy Text
class PostsController < ApplicationController 
    def index 
      @posts = Post.accessible_by(current_ability)
                   .where(type: :video)
                   .where('view_count > ?', 100), 
    end
end

The problems in the above code are:

  • This code isn’t reusable
  • It’s hard to test the logic
  • Any changes to the post schema can break this logic/code.

To make the controller skinny, readable, and neat, we can use scopes:

Copy Text
class Post < ActiveRecord::Base,
   scope : video_type, ->{ where(type: :video) } 
   scope : popular, -> { where('view_count > ?', 1000) }
   scope : popular_video_type, -> { popular.video_type } 
end

So, the controller will look like that:

Copy Text
class Articles Controller < ApplicationController 
   def index 
     @posts = Post.accessible_by(current_ability),
                  .popular_video_type 
     end 
end

But still, it is not an appropriate solution; here we need to create scopes for every query condition we want to add, we are also increasing the code in the Model with various combinations of scope for diverse use cases.

To solve this kind of problem, we use the Query Object:

// video_query.rb

Copy Text
class VideoQuery 
   def call(ability) 
      ability
        .where(type: : video) 
        .where('view_count > ?', 1000)
   end
end

// post_controller.rb

Copy Text
class PostsController < ApplicationController 
   def index
      ability = Post.accessible_by(current_ability),
      @articles = VideoQuery.new.call(ability) 
   end 
end

Now, it’s reusable! We can use this class to query any other models that have a similar scheme.

4. Decorators

Another rails design pattern is Decorator. The decorator is a design pattern that allows the behavior to be added to an object, dynamically, without disturbing the behavior of other objects of the same class. Decorators can be useful for cleaning up logic/code written inside the view and controller in an RoR application.

Process:

  • Create an app/decorator folder.
  • Add decorate helper in ApplicationHelper.
Copy Text
module ApplicationHelper 
  def decorate(model_name, decorator_class = nil),
    (decorator_class || "#{model_name.      class}Decorator".constantize).new(model_name), 
     end 
end
  • Add base_decorator.rb in app/decorators folder.
Copy Text
class BaseDecorator < SimpleDelegaton 
def decorate (model_name, decorator_class = nil),
ApplicationController.helpers.decorate(model_name, decorator_class), 
end 
end
  • Add user_decorator.rb in app/decorators folder.
Copy Text
class UserDecorator < BaseDecorator
  def full name 
    "#{fname} #{lname}"
  end 
end
  • Initialize @user_decorator in your user_controller.
Copy Text
class users Controller < Application Controller 
  def show
     @user_decorator = helpers. decorate(current_user), 
  end
end

Let’s use this in our view(show.html.erb).

Copy Text
<%= @user_decorator.full_name %> 
<%= @user_decorator.email %>

5. Form Objects

The form object is a design pattern that is used to encapsulate the code related to validation and persisting data into a single unit. Let’s have a look at the example of the Form Objects. Let’s assume that we have a rails post model and a controller (posts_controller) action for creating the new post. Let’s discuss the problem, Here Post Model contains all validation logic, so it’s not reusable for other entities, e.g., Admin. app/controller/posts_controller.rb

Copy Text
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(:title, :description, :content)
   end
end

// app/model/post.rb

Copy Text
class Post < ActiveRecord: :Base
validates :title, presence: true
validates : content, presence: true 
end

The better solution is to move the validation logic to a separate singular responsibility class that we might call PostForm:

Copy Text
class Post Form
include ActiveModel: :Model 
include Virtus.model

attribute :id, Integer 
attribute :title, String 
attribute :description, String 
attribute : content, String

attr_reader : record

def persist
     @record = id ? Post. find(id) : Post.new

    if valid?
      @record. save!
       true
    else
       false 
    end 
  end 
end

Now, We can use it inside our posts_controller like that:

Copy Text
class PostsController < ApplicationController 
   def create
     @form = Post Form.new(post_params)

if @form. persist
render json: @form. record 
else
render json: @form.errors, status: :unpocessably_entity
   end
end

private

def post_params
     params. require(: post).permit(:title, :description, :content)
   end
end

6. Value Object

The Value object is a type of Ruby pattern that encourages small, simple objects and allows you to compare these objects as per the given logic or specific attributes. It represents value but not something unique in your system like a user object. Value Objects always return only values. Let’s have a look at the example for better understanding:

Copy Text
class EmailReport 
    def initialize(emails)
      @emails = emails
 end

def data
     emails_data = [ ]

emails.each do email| 
emails_data << {
     username: email.match(/([^@]*)/).to_s, 
     domain: email.split("@"). last
}
end

emails_data 
end

private 
attr_reader : emails
end

We are doing the following thing with the email:

  • We are not changing the email value
  • We return only values.

Now, let’s create the value object:

Copy Text
class Email 
    def initialize(email)
     @email = email 
end

def username
    email.match(/([^@]*)/).to_s 
end

def domain
    email.split("a"). last 
end

def to_h
    { username: username, domain: domain } 
end

private
attr_reader : email 
end

Now, we just need to use Email value object link this:

Copy Text
class EmailReport 
    def initialize(emails: emails),
        @emails = emails 
   end

def data
    emails.map { email| Email.new(email).to_h }, end

private
attr_reader : emails 
end

7. Policy Object

The policy object is similar to the Service object; the only difference is policy object is responsible for the read operations, and the service object is responsible for write operations. In rails, we use cancan or pundit gem for the authorization, but these gems are suitable if the application complexity is medium, If the application complexity is high (in terms of authorization), then, in that case, we use policy object for better efficiency. It returns a boolean value (true or false). Let’s have a look at the below example for a better understanding.

Copy Text
class UserService 
      def initialize(user)
        @user = user
end

def name
     user_policy.take_email_as_name?? user.email : user.full_name
end

def account_name
    user_policy.is_admin? ? "Administrator": "User",
end
private 
attr_reader : user

def user_policy
@_user_policy II = UserPolicy.new(user), 
   end
end

Let’s create a policy (app/policies/user_policy.rb):

Copy Text
class UserPolicy 
      def initialize(user)
        @user = user 
end

def is_admin?
     user_role_is_admin? 
end

def take_email_as_name?
      user_full_name_is_not_present? && is_user_email_present? 
end

private 
attr_reader : user

def user_full_name_is_not_present?
     user.full_name.blank? 
end

def is_user_email_present?
     user.email.present? 
end

def user_role_is_admin?
user.sign_in_count > 0 && user.role == "admin" 
     end 
end

8. Builder

With the help of the Builder pattern, we can construct complex objects without much effort. We can call it an Adapter, whose main purpose is to untangle the complexity of the object instantiation process. Whenever you are dealing with a highly customized product and its complexity the Builder pattern helps to clear the clutter to create the basic concept. Because of it, one can have a fundamental understanding of complex construction.

When to use the Builder pattern?

  • When you are creating new objects and you need many permutations of that particular feature
  • When you are focused on the process of creating objects and how to assemble them, rather than being dependent on just constructors.

9. Interactor

Our next rails design pattern is interactor. Now, the question is what is the interactor design pattern. So, when you want to disintegrate large and complicated tasks into smaller and inter-dependent steps, you can go with an interactor design pattern. Whenever, a step fails, the flow will stop by itself and will display a relevant message of failed execution.

When to use the Interactor pattern?

  • When you intend to breakdown a large task or process into smaller ones
  • When you want to have the flexibility to make changes often in the substeps
  • When you want to integrate external APIs into the smaller tasks

Example of Interactor Design Pattern

In the example, we will look at how can we disintegrate the process of purchasing a product from an e-commerce website.

Copy Text
class ManageProducts
    include Interactor
    
    def call
        # manage products and their details
    end
end

class AddProduct
    include Interactor
    
    def call
        # Add product to purchase
    end
end

class OrderProduct
    include Interactor
    
    def call
        # Order product to purchase
    end
end

class DispatchProduct
    include Interactor
    
    def call
        # dispatch of the product here
    end
end

class ScheduleMailToNotify
    include Interactor

    def call
        # send an email to the respective buyer
    end
end

class PurchaseProduct
    include Interactor::Organizer

organize ManageProducts, AddProduct, OrderProduct, DispatchProduct, ScheduleMailToNotify
end

Here, is how you can purchase the product.

result = PurchaseProdcut.call(
    recipient: buyer, product: product
)

puts outcome.success?
puts outcome.message

10. Observer

Moving on to the last ruby design pattern, i.e., the Observer design pattern. In this pattern, other interested objects are notified whenever an event has occurred. The observed object contains its observers’ list and notifies them by sending an update whenever its state changes.

When to use the Observer pattern?

  • When you want to make several views changes manually
  • When an object’s state is dependent on a specific state
  • When several views are dependent on a particular object’s state

Example of Observer Design Pattern

Copy Text
require 'observer'

class Product
  include Observable

  attr_reader :productName, :availableProducts

  def initialize(productName = "", availableProducts = 0)
    @productName, @availableProducts = productName, availableProducts
    add_observer(Notifier.new)
  end

  def update_availableProducts(product)
    @product = product
    changed
    notify_observers(self, product)
  end
end

Now, we will build a class which would be notified with the updates on available products.

Copy Text
class Notifier
  def update(product, availableProducts)
    puts "Yes, #{product.name} is available" if availableProducts > 0
    puts "Oops! Sorry, #{product.name} is unavailable!" if availableProducts == 0
  end
end

Let’s put them together

Copy Text
isProductAvailable = Product.new("Iphone 12")

isProductAvailable.update_availableProducts(5)
# => Yes, Iphone 12 is available

isProductAvailable.update_availableProducts(8)
# => Yes, Iphone 12 is available

isProductAvailable.update_availableProducts(0)
# => Oops! Sorry, Iphone 12 is unavailable

Conclusion

There are other design patterns in Ruby on Rails, but these ten are the fundamentals and most needed rails design patterns. I hope the blog has served your purpose as you expected. Feel free to use these design patterns in Rails app and see the results by yourself. If you are an RoR enthusiast, then surely visit the RoR tutorials page and start learning about Rails. If you have any questions, feedback, or suggestions, contact us!

Ruby On Rails Tutorials

EXPLORE NOW

Get In Touch

[email protected]

Your Success Is Guaranteed !

We accelerate the release of digital product and guaranteed their success

We Use Slack, Jira & GitHub for Accurate Deployment and Effective Communication.

How Can We Help You?