12/08/2018, 17:09

5 cách để refactor Rails View

1. Partials Partial được xây dựng trong Rails được sử dụnng để dùng nhóm các logic, html dùng chung. có thể sử dụng lại trong suốt ứng dụng. Một ví dụ điển hình sử dụng partial là: # app/views/employees/new.html.erb <h1>New Employee</h1> <%= render 'form' %> <%= ...

1. Partials

Partial được xây dựng trong Rails được sử dụnng để dùng nhóm các logic, html dùng chung. có thể sử dụng lại trong suốt ứng dụng. Một ví dụ điển hình sử dụng partial là:

# app/views/employees/new.html.erb

<h1>New Employee</h1>  
<%= render 'form' %>  
<%= link_to 'Back', employees_path %> 

Nếu đang ở trang index chứa các bản ghi employee, bạn có thể viết ngắn gọn:

<%= render @employees %>  

Khi chúng ta có 1 partial tên là _employee và sử dụng chúng để render từng employee trong @employees

Tìm hiểu thêm tại:

  • Layouts and Rendering in Rails (Official Docs)
  • Action View Partials (Official Docs)
  • Partials in Ruby on Rails

2. Decorators

Chúng ta sử dụng decorator để viết những logic không thuộc về model, cũng không thuộc về view.

Một ví dụ ở View:

<article>  
  <span class="publication-status">
    <% if @article.published? %>
      Published at: <%= @article.published_at.strfitme("%B #{@article.published_at.day.ordinalize}, %Y")
    <% else %>
      Draft
    <% end %>
  </span>
...
</article>

Khi viết nhiều code logic trong view, nó sẽ trở lên phức tạp, khó quản lí view của mình. Rails đã cung cấp cho chúng ta Rails Helper như 1 nơi chứa các logic của View, nhưng helper có nhược điểm:

  • Helpers có thể sử dụng trên tất cả các view, chúng dễ dàng bị conflict tên hàm với nhau
  • Helper là module, do vậy chúng ta không thể truy cập đối tượng. Có nghĩa là phải truyền đối tượng vào như một tham số trong hàm helper như: article_published_at_date(article)

Gem Draper là một gem phổ biến dùng để cung cấp các decorator pattern (mở rộng của Active Record object) giúp trình bày các logic mà không cần viết vào model.

Chúng ta sử dụng Draper cho ví dụ trên:

class ArticleDecorator < Draper::Decorator  
  delegates_all

  def publication_status
    if is_published?
      "Published at: #{published_at}"
    else
      "Draft"
    end
  end

  def published_at
    object.published_at.strfitme("%B #{published_day}, %Y")
  end

  private

  def published_day
    object.published_at.day.ordinalize
  end
end  

Để dùng decorator này. chúng ta phải thêm vào controller như sau:

class ArticlesController < ApplicationController  
  def show
    @article.find(params[:id]).decorate
  end
end

Ở view sẽ sửa thành:

<article>  
  <span class="publication-status">
    <%= @article.publication_status %>
  </span>
...
</article>  

Như vậy view đã trở nên ngắn gọn hơn, không có logic phức tạp. Chúng ta có thể nhìn lướt qua view và hiểu nó đang hiển thị gì một cách dễ dàng Tìm hiểu thêm về Gem này tại:

  • Draper Gem
  • Experimenting with Draper
  • Decorators on Rails
  • Rails Presenters

3. Null Object

Sử dụng Null Object để tránh các điều kiện phức tạp. Tìm hiểu qua một ví dụ: Chúng ta có 1 ứng dụng mà user có thể xem nếu nó là amin hoặc customer, hoặc là guest nếu họ chưa login. Phụ thuộc vào role của user, chúng sẽ hiển thị các view header khác nhau:

<% if user_signed_in? %>  
  <% if current_user.role == 'admin' %>
    <%= render 'admin_nav' %>
  <% elsif current_user.role == 'customer' %>
    <%= render 'customer_nav' %>
  <% end %>
<% else %>  
  <%= render 'guest_nav' %>
<% end %> 

Tại ví dụ này, chúng ta đang sử dụng partial. Nhưng nếu ta add thêm 1 role mới, ta lại phải thêm 1 điều kiện nữa ở view. Chúng ta có thể làm ngắn gọn hơn bằng cách:

<% if user_signed_in? %>  
  <%= render "#{current_user.role}_nav" %>
<% else %>  
  <%= render 'guest_nav' %>
<% end %>  

Thật đơn giản phải không? Nhưng ta chưa xét đến trường hợp null. Nếu current_user không có 1 role nào cả. Trường hợp này ta có thể sử dụng Null Object. Null Object là một đối tượng Ruby cũ mà respond trả về giống với đối tượng không phải là null. Có nghĩa chúng ta có thể làm với Null Object giống cách mà chúng ta làm với 1 object bình thường. Như trong ví dụ trên, chúng ta sẽ bắt đầu với 1 class đặt tên cho nó là Guest

class Guest  
  def role
    'guest'
  end
end  

Guest class sẽ response .role là guest, giống như User respond .role là admin hoặc customer Ở trong ApplicationController nơi mà current_user đã được định nghĩa. Chúng ta sẽ return 1 instance của Guest thay vì nil khi user không login:

class ApplicationController  
  def current_user
    super || Guest.new
  end
end

Lúc này, chúng ta có thể refactor đoạn view ở ví dụ trên thành

<%= render "#{current_user.role}_nav" %>  

Tham khảo thêm tại:

  • Rails Refactoring Example: Introduce Null Object
  • Naught Gem

4. Form Object

Form Object sử dụng để đơn giản hóa form có nhiều model và các form có logic phức tạo khác. Thay vì sử dụng accepts_nested_attributes_for và phải xử lý các validation fail của nhiều model, form object cho phép chúng ta có thể nhóm form lại và validation ở một chỗ khác. Do vậy Form object giúp chúng ta giảm thiểu tình trạng "Fat model", đồng thời giúp code sáng sủa hơn, validates dễ dàng hơn.

Ví dụ: Chúng ta có 1 model Survey. mỗi survey có nhiều question (Model: Question), bạn có thể sử dụng Form Object để tạo Survey. Ta sẽ sử dụng Virtus tạo 1 đối tượng giống như Active Record. Nhưng có nhiều option cho tạo Form Object.

class CreateSurvey  
  include Virtus

  extend ActiveModel::Naming
  include ActiveModel::Conversion
  include ActiveModel::Validations

  attribute :title, String
  attribute :questions, Array[String]

  validates :title, presence: true

  def save
    if valid?
      persist!
      true
    else
      false
    end
  end

  private

  def persist!
    transaction do
      @survey = Survey.create!(title: title)
      @questions = questions.map{|question_text| Question.create(text: question_text)
    end
  end
end  

Trong Controller, chúng ta có thể kiểm soát form submit bằng các sử dụng object CreateSurvey

SurveysController < ApplicationController  
  def create
    @survey = CreateSurvey.new(params[:survey])

    if @survey.save
      # logic if successful
    else
      # logic if unsuccessful
    end
  end
end 

Để tìm hiểu rõ hơn về Form Object, bạn có thể tham khảo tại:

  • Reform Gem
  • Form Object Validations in Rails 4
  • Form Objects with Virtus
  • Form Objects (RailsCast)
  • 7 Patterns to Refactor Fat ActiveRecord Models

5. Alternate Templating Languages

ERB là một ngôn ngữ mẫu hoàn hảo, nhưng những ngôn ngữ tiên tiến hơn như Haml và Slim cho phép bạn viết HTML với Ruby trở nên rõ ràng hơn.

Nhìn vào ví dụ nhỏ để thấy sự khác biệt:

ERB:

<section class=”container”>  
  <h1><%= post.title %></h1>
  <h2><%= post.subtitle %></h2>
  <div class=”content”>
    <%= post.content %>
  </div>
</section>  

Haml

%section.container
  %h1= post.title
  %h2= post.subtitle
  .content
    = post.content

Slim

section.container  
  h1= post.title
  h2= post.subtitle
  .content= post.content

Việc lựa chon template language nào tùy thuộc vào sở thích của mỗi người. Nếu bạn thấy thoải mái với HTML tags, ERB là 1 sự lựa chọn tốt (Cung cấp nhiều hỗ trợ nhất). Nếu bạn muốn viết 1 cách ngắn gọn nhất thì chọn Slim.

Tìm hiểu thêm tại:

  • Haml
  • Slim

Hi vọng bài viết có thể giúp ích cho bạn!

Nguồn tham khảo:

https://allenan.com

0