---
# System prepended metadata

title: Rails design parttern

---

# Rails design parttern

### 1. Service object

#### Mục đích

- Để xử lý logic của hành động.
- Không để models / controllers phình to.
- Không để logic phức tạp vào views.
- Khả năng tái sử dụng tốt hơn, dễ hơn trong việc maintain

#### Cách hoạt động

- Nhận input đầu vào params, tham số ....
- Thực hiển xử lý logic, chức năng.
- Trả về result.

#### Ví dụ
- Tính toán lương
- Import/export dữ liệu
 

### 2. View object(Presenter)

#### Mục đích

- Đóng gói các logic liên quan tới models & view. Giúp có model/view gọn gàng hơn.
- Dễ dàng viết test hơn vì sử dụng class riêng.
- Hỗ trợ view không dài dòng, phức tạp. 

#### Ví dụ

```erb
<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>
```

Dài dòng ở view, khó đọc.

=> Gói gọn xử lý trong presenter.

```ruby
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

```

```erb
<% presenter = UserPresenter.new(user) %> 
<p>
  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}" %>
</p>
```

### 3. Decorator

#### Mục đích

- Cho phép chùng ta thêm các hành động vào 1 đối tượng cụ thể mà không ảnh hưởng tới các đối tượng khác, tránh phình to model.
- Làm sạch logic/code được viết trong views/model/service ...

#### Ví dụ

```ruby
class CandidateDecorator < Draper::Decorator
  delegate_all

  def full_name
    if first_name.blank? && last_name.blank?
        "No name is provided"
    else
        "#{first_name} #{last_name}"
    end
  end

  def link_to_candidate
   link_to candidate.full_name, candidate
  end
end

```

```ruby
class CandidatesController < ApplicationController
  def index
    @candidates = Candidate.all.decorate
  end

  def show
   @candidate = Candidate.find(params[:id]).decorate # Chỉ thêm các method vào object
  end
end
```

```erb
<p id="candidate-<% @candidate.id %>"> Candidate information </p>
<p>
  <strong>Full name:</strong>
  <%= @candidate.link_to_candidate %>
</p>
```

### 4. Query object

#### Mục đích

- Tách query logic ra khỏi controller/model/service và có thể tái sử dụng. 
- Trả về *ActiveRecord::Relation* 
- Có thể dễ dàng được Test một cách độc lập.
- Chỉ đại diện cho truy vấn database, không phải logic hay thao tác nào đó.

#### Ví dụ

```ruby
class UsersListQuery
  def self.inactive
    User.where(sign_in_count: 0, verified: false)
  end

  def self.active
    User.where(verified: true).where("users.sign_in_count > 0")
  end

  def self.most_active
    # some more complex query
  end
end
```

### 5. Value object

#### Mục đích

- Sử dụng với các object nhỏ đơn giản.
- Đại diện cho giá trị nhưng không phải là một cái gì đó duy nhất trong hệ thống của bạn như kiểu user object.
- Chia nhỏ, cô lập các chức năng. => Dễ dàng viết test.
- Loại bỏ lặp code, cải thiện, clean code. Nhóm các hành động liên quan tới 1 loại dữ liệu vào 1 class.


#### Ví Dụ:

- Không thay đổi giá trị gốc.
- Luôn trả về giá trị. 

```ruby
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
```

=>

```ruby
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
```

```ruby
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
```

### 6. Builder
#### Mục đích
- Giúp cho việc xây dựng đối tượng dễ dàng hơn.
- Đóng gói việc xây dựng đối tượng vào 1 class.

#### Ví dụ

```ruby
class Computer
  attr_accessor :display
  attr_accessor :motherboard
  attr_reader :drives
  
  def initialize(display=:crt, motherboard=Motherboard.new, drives=[])
    @motherboard = motherboard
    @drives = drives
    @display = display
  end
end
```

```ruby
class ComputerBuilder
  attr_reader :computer

  def initialize
    @computer = Computer.new
  end

  def turbo(has_turbo_cpu=true)
    @computer.motherboard.cpu = TurboCPU.new
  end

  def display=(display)
    @computer.display=display
  end

  def memory_size=(size_in_mb)
    @computer.motherboard.memory_size = size_in_mb
  end

  def add_cd(writer=false)
    @computer.drives << Drive.new(:cd, 760, writer)
  end

  def add_dvd(writer=false)
    @computer.drives << Drive.new(:dvd, 4000, writer)
  end

  def add_hard_disk(size_in_mb)
    @computer.drives << Drive.new(:hard_disk, size_in_mb, true)
  end
end

builder = ComputerBuilder.new
builder.turbo
builder.add_cd(true)
builder.add_dvd
builder.add_hard_disk(100000)
```

### 7. Form Object
#### Mục đích
- Validate 1 object có thể không phải là model.
- Giảm tải code cho model/controller, Có thể tái sử dụng

#### Ví dụ

```ruby
class SearchForm
  include ActiveModel::Model

  attr_accessor :search_params

  validates :search_params, presence: true
end
```

Validate search_params luôn có giá trị được gửi từ client.



### 8. Policy object

Mục đích.
- Định nghĩa quyền truy cập tới action trong controller
- Giúp tiếp cận rõ ràng và dễ dàng quản lý các quyền truy cập.
- Tách biệt phần phân quyền với controller. => dễ đọc hiểu dễ bảo trì, mở rộng. 

Ví dụ

```ruby
# app/policies/post_policy.rb
class PostPolicy
  attr_reader :user, :post

  def initialize(user, post)
    @user = user
    @post = post
  end

  def create?
    user.admin? || user.moderator?
  end

  def update?
    user.admin? || (user.moderator? && post.user_id == user.id)
  end

  def destroy?
    user.admin? || (user.moderator? && post.user_id == user.id)
  end
end

```

```ruby
class PostsController < ApplicationController
  def create
    @post = Post.new(post_params)
    authorize @post, :create?

    if @post.save
      # ...
    else
      # ...
    end
  end

  def update
    @post = Post.find(params[:id])
    authorize @post, :update?

    if @post.update(post_params)
      # ...
    else
      # ...
    end
  end

  def destroy
    @post = Post.find(params[:id])
    authorize @post, :destroy?

    @post.destroy
    # ...
  end

  # ...
end
```


### 9. Observer

Mục đích

- Cho phép 1 đối tượng theo dõi và phản ứng với sự thay đổi của đối tượng khác.
- Trong Rails, mẫu Observer có thể được thực hiện bằng cách sử dụng các callback của Active Record.

Ví dụ

```ruby=
class PostObserver1 < ActiveRecord::Observer
  def after_create(post)
    # thực hiện một số thứ sau khi bài viết được tạo
  end
end

class PostObserver2 < ActiveRecord::Observer
  def after_update(post)
    # thực hiện một số thứ sau khi bài viết được cập nhật
  end
end
```

```ruby=
config.active_record.observers = [:post_observer1, :post_observer2]
```

Khi một đối tượng Post được tạo hoặc cập nhật, Rails sẽ tự động gọi các phương thức của PostObserver1, PostObserver2.


### 10. Interactor
Mục đích

- Tách logic xử lý phức tạp ra khỏi controller và model trong Rails.

Điểm khác so với service
- "Interactor" thường được triển khai bằng cách sử dụng các thư viện hỗ trợ như interactor-rails, organizer,...
- Đặc trưng cho các tác vụ chứa nhiều bước xử lý liên quan đến việc thực hiện một nhiệm vụ hoàn chỉnh, có thể bao gồm nhiều service và thao tác.

Ví dụ

```ruby=
# app/interactors/create_user_interactor.rb
class CreateUserInteractor
  include Interactor

  def call
    user = User.create(context.user_params)
    if user.valid?
      context.user = user
    else
      context.fail!(error: user.errors.full_messages)
    end
  end
end

# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def create
    result = CreateUserInteractor.call(user_params: params[:user])
    if result.success?
      redirect_to root_path, notice: 'User created successfully'
    else
      flash.now[:alert] = result.error.join(', ')
      render :new
    end
  end
end

```

### 11. Adapter

Mục đích
- Adapter thường được sử dụng khi bạn cần tương tác với một thư viện hoặc dịch vụ bên ngoài(API ngoài hệ thống) mà có một interface không tương thích với interface của ứng dụng Rails của bạn.
- giúp tách biệt tác vụ tương tác với các dịch vụ bên ngoài và tăng tính mô đun hóa và bảo trì.

```ruby=
# ExternalServiceAdapter.rb
class ExternalServiceAdapter
  def initialize(api)
    @api = api
  end

  def fetch_data
    response = @api.get('/data')
    # Xử lý response theo cách phù hợp với ứng dụng Rails của bạn
  end
end

# ExternalServiceClient.rb
class ExternalServiceClient
  def get(endpoint)
    # Gửi HTTP request đến dịch vụ ngoại vi và nhận response
  end
end

# In your Rails controller
class DataController < ApplicationController
  def show
    external_api = ExternalServiceClient.new
    adapter = ExternalServiceAdapter.new(external_api)
    data = adapter.fetch_data
    render json: data
  end
end
```