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