Ruby on Rails
The main principle of software development is to keep the code DRY (Don’t Repeat Yourself) to reduce the repetition within the code. So, Ruby on rails has some design patterns that allow us to reuse our code. Though we'll use models, helpers, and concerns to make the code dry, if the code complexity is higher or we are using external API, we use Rails Design Patterns.
Here are a few design patterns that you can follow with Ruby on Rails:
Service is the most used design pattern in Rails applications. You can use your service in multiple places, like models, controllers, jobs, etc, keeping your application clean and DRY.
When to use service objects?
Default Rails app structure doesn’t have any directory where we can put our services.
Following are the steps to create one
A simple service file looks like this.
class ServiceName
attr_accessor :first_variable, :second_variable
def initialize(first_variable, second_variable)
@first_variable = first_variable
@second_variable = second_variable
end
def call
method_name
end
private
def method_name
# method body
end
end
Methods initialize and call are a must-have. If the call method would be too long or complicated, it’s best to create additional methods in the private section. That’s because only the call method should be available outside of the service.
In OOP, the decorator gives us the ability to increase a particular object’s behavior by equipping it with some additional methods.
Decorators can be useful for cleaning up logic/code written inside the view and controller in a Rails application. This design pattern is being used with a draper gem.
Draper is beneficial once we have methods in models, which are used only in views.
Let’s have a glance at the below example:
If in a User model we have a method full_name.
def full_name
"#{first_name} #{last_name}"
end
We should move it into a UserDecorator.
Following are the steps to use a decorator
class UserDecorator < Draper::Decorator
delegate_all
def full_name
"#{object.first_name} #{object.last_name}"
end
end
Great thing is that not every user object in views will have that method, We've to embellish that object before sending it to the view, simply using user.decorate. So in the controller, we've to use:
def show
@user = User.find(params[:id].to_i).decorate
end
And then, we will use this code in our views:
<%= @user.full_name %>
View Objects allow us to encapsulate all view related logic and keep both models and views neat. The View shouldn't contain calculation logic. To unravel the calculation logic problem, we'll use a rails helper, but if the complexity of the code is high, therein case, we should always use the Presenter.
Let’s have a glance at the below example:
<td><%= "#{@user.first_name} #{@user.last_name}" %></td>
<td><%= link_to "View more", user, class: "text-#{user.active? ? 'green' : 'red'} border-#{user.active? ? 'green' : 'red'}" %></td>
Here we'll see that we are concatenating the user’s first_name and last_name on the view. Which isn't the best practice. Hence to unravel this problem, we should always use the presenter.
Following are the steps to use Presenter:
class UserPresenter
def initialize(user)
@user = user
end
def full_name
"#{@user.first_name} #{@user.last_name}"
end
def css_color
@user.active? ? 'green' : 'red'
end
end
Now let’s change the view to make it more readable and with no calculations.
<% presenter = UserPresenter.new(user) %>
<td><%= presenter.full_name %></td>
<td><%= link_to "View more", user, class: "text-#{presenter.css_color} border-#{presenter.css_color}" %></td>
Query Object could also be a kind of design pattern that lets us fetch query logic from Controllers and Models into reusable classes.
Let’s have a glance at the below example:
class ActivityLogsController < ApplicationController
def index
@activity_logs = ActivityLog.where(is_important: true).order(created_at: :desc)
end
end
The problems in the above code:
To make the controller skinny, readable, and neat we will use scopes:
class ActivityLog < ApplicationRecord
scope :by_most_recent, -> { order(created_at: :desc) }
scope :marked_important, -> { where(is_important: true) }
end
So, the controller will appear something like this:
class ActivityLogsController < ApplicationController
def index
@activity_logs = ActivityLog.marked_important.by_most_recent
end
end
Form Object is a design pattern that encapsulates logic associated with validating and persisting data.
Let’s assume we’ve got a typical Rails Model and Controller action for creating new users.
The Model contains all validation logic, so it’s not reusable for other entities, e.g. Admin
class UsersController < ApplicationController
def create
@user = User.new(user_params)
if @user.save
render json: @user
else
render json: @user.error, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:email, :full_name, :password, :password_confirmation)
end
end
class User < ActiveRecord::Base
EMAIL_REGEX = /@/
validates :full_name, presence: true
validates :email, presence: true, format: EMAIL_REGEX
validates :password, presence: true, confirmation: true
end
The better solution is to move the validation logic to a separate singular responsibility class that we might call UserForm:
class UserForm
EMAIL_REGEX = /@/
include ActiveModel::Model
include Virtus.model
attribute :id, Integer
attribute :full_name, String
attribute :email, String
attribute :password, String
attribute :password_confirmation, String
validates :full_name, presence: true
validates :email, presence: true, format: EMAIL_REGEX
validates :password, presence: true, confirmation: true
attr_reader :record
def persist
@record = id ? User.find(id) : User.new
if valid?
@record.attributes = attributes.except(:password_confirmation, :id)
@record.save!
true
else
false
end
end
end
Now, We can use it inside our users_controller like that:
class UsersController < ApplicationController
def create
@form = UserForm.new(user_params)
if @form.persist
render json: @form.record
else
render json: @form.errors, status: :unpocessably_entity
end
end
private
def user_params
params.require(:user).permit(:email, :full_name, :password, :password_confirmation)
end
end
As a result, the user Model is not any longer liable for validating data:
class User < ActiveRecord::Base
end
Design patterns in your codes can be an effective way to manage complexity. These concepts should give you a basic understanding of when and how you can refactor your code.
Rails is most suitable framework to build your startup.
Rails is a go-to web framework when you want to create a complex web application. But when it comes to APIs there are many players like Roda, Sinatra, Padrino, Grape and, Rails API.
A universally unique identifier (UUID) is a 128-bit number used to identify information in computer systems. Sometimes it is referred to as a 'globally unique identifier'.
We value your privacy
We use cookies and other tracking technologies to enhance your experience on our website. We may store and/or access information on a device and process personal data, such as your IP address and browsing data, for personalized advertising and content, advertising and content measurement, audience research, and services development. Additionally, we may use precise geolocation data and identification through device scanning. Please note that your consent will be valid across all our subdomains. You can change or withdraw your consent at any time. We respect your choices and are committed to providing you with a transparent and secure browsing experience.