10 Ruby on Rails Best Practices
Ruby on Rails là một web framework được viết bằng ngôn ngữ lập trình Ruby. Nhưng sự khác biệt là nó có rất nhiều công cụ giúp tăng tốc quá trình phát triển và làm cho công việc được dễ dàng hơn. cho phép chúng ta tập trung vào nhiệm vụ chứ không phải là công nghệ. Nhưng để làm việc tốt với ...

Ruby on Rails là một web framework được viết bằng ngôn ngữ lập trình Ruby. Nhưng sự khác biệt là nó có rất nhiều công cụ giúp tăng tốc quá trình phát triển và làm cho công việc được dễ dàng hơn. cho phép chúng ta tập trung vào nhiệm vụ chứ không phải là công nghệ. Nhưng để làm việc tốt với Rails đặc biệt đối với người mới bắt đầu là rất quan trọng. Trong bài viết này mình sẽ giới thiệu về môt số best practices in Ruby on Rails.
1. Tow Space Indentation
Đây là một trong những hướng dẫn phong cách thích nghi rộng rãi nhất và thoả thuận trong cộng đồng Ruby. Sử dụng 2 thụt vào không gian thay vì 4 không gian thụt đầu dòng. Chúng ta hãy xem một ví dụ:

2. Define Predicate Methods with a ?
Trong Ruby, chúng ta có một quy ước cho các phương thức trả về giá trị true or false kết thúc bằng dấu ?. Trong một số ngôn ngữ bạn sẽ thấy phương thức hoạc các tên biến được định nghĩa như is_valid or is_paid, etc. Ruby không khuyến khích phong cách này và nó theo một cách ngôn ngữ của con người giống như object.valid? or fee.paid? (chú ý không dùng tiền tố is_).
3. Iteration: Use each Instead of for
Hầu như tất cả các lập trình viên của Ruby sẻ dụng each thay vì for.
for
for i in 1..100 ... end
each
(1..100).each do |i| ... end
4. Conditionals: Use unless Instead of !if
Bad
    if !true
      do_this
    end
    if name != "sarmad"
      do_that
    end
Good
    unless true
      do_this
    end
    unless name == "sarmad"
      do_that
    end
Bad
unless user.save
else
end
Good
if user.save
else
end
5. Short Circuits**
Trong 1 số ngôn ngữ lập trình, biểu thức logic thường có tính đoản mạch (Short-circuit). hãy xem ví dụ này:
if user.gender == "male" && user.age > 17 do_something elsif user.gender == "male" && user.age < 17 && user.age > 5 do_something_else elsif user.age < 5 raise StandardError end
Trong trường hợp này, nó cần phải kiểm tra tất cả các điều kiện để tìm user dưới 5 tuổi và raise một exception. Cách tốt hơn là:
raise StandardError if user.age < 5 if user.gender == "male" && user.age > 17 do_something elsif user.gender == "male" && user.age < 17 #we saved a redundant check here do_something_else end
=> Nó là hiệu quả hơn để return sớm.
6. DRY (Don’t Repeat Yourself)
Khi viết code bạn suy nghĩ cách tối ưu nhất để đảm bảo rằng bạn Don’t Repeat Yourself, Trách sự trùng lặp nhiều. Hãy xem qua ví dụ này:
class Mercedes
  def accelerate
    "60MPH in 5 seconds"
  end
  def apply_brakes
    "stopped in 4 seconds"
  end
  def open_boot
    "opened"
  end
  def turn_headlights_on
    "turned on"
  end
  def turn_headlights_off
    "turned off"
  end
end
class Audi
  def accelerate
    "60MPH in 6.5 seconds"
  end
  def apply_brakes
    "stopped in 3.5 seconds"
  end
  def open_boot
    "opened"
  end
  def turn_headlights_on
    "turned on"
  end
  def turn_headlights_off
    "turned off"
  end
end
Chúng ta có 3 methods trùng lặp, open_boot, turn_headlights_on, và turn_headlights_off. Để giải quyết vấn đề DRY chúng ta hãy sử dụng class inheritance and/or abstract classes. Chúng ta viết lại:
class Car
  def open_boot
    "opened"
  end
  def turn_headlights_on
    "turned on"
  end
  def turn_headlights_off
    "turned off"
  end
end
class Mercedes < Car
  def accelerate
    "60MPH in 5 seconds"
  end
  def apply_brakes
    "stopped in 4 seconds"
  end
end
class Audi < Car
  def accelerate
    "60MPH in 6.5 seconds"
  end
  def apply_brakes
    "stopped in 3.5 seconds"
  end
end
7. Smart Use of Enums
Enum là kiểu dữ liệu liệt kê, giúp bạn tổ chức dữ liệu khoa học hơn, code được trong sáng dễ hiểu hơn. Ta có một ví dụ: Giả sử bạn một model là Book và có column status để lưu trữ các status (draft, completed, published).
if book.status == "draft" do_something elsif book.status == "completed" do_something elsif book.status == "published" do_something end
if book.status == 0 #draft do_something elsif book.status == 1 #completed do_something elsif book.status == 2 #published do_something end
=> ở đây bạn nên dùng Enums. define column status là integer (not null) và tạo một giá trị default mà bạn muốn.( after_create). Bây giờ, bạn define một enums trong model Book như thế này:
enum status: { draft: 0, completed: 1, published: 2 }
Và bạn có thể viết lại đoạn code trên:
if book.draft? do_something elsif book.completed? do_something elsif book.published? do_something end
Giờ code của bạn trông thật rõ ràng phải không nào. Ngoài ra nó cũng còn cung cấp cho bạn những phương pháp để update status.
book.draft! book.completed! book.published!
8. Fat Models, Skinny Controllers and Concerns
ROR áp dụng kiến trúc MVC để xây dựng framework. Nên để làm việc hiểu quả chúng ta cần phải hiêu về MVC và cách làm việc hiệu quả với MVC. Một ví dụ cho chúng ta thấy về MVC.
class BooksController < ApplicationController before_action :set_book, only: [:show, :edit, :update, :destroy, :publish]
  def publish
    @book.published = true
    pub_date = params[:publish_date]
    if pub_date
      @book.published_at = pub_date
    else
      @book.published_at = Time.zone.now
    end
    if @book.save
else
end end
private
    def set_book
      @book = Book.find(params[:id])
    end
end
Nếu như chúng ta xử lý các logic của model ở controller là không hợp lý. chúng ta move các logic này vào trong model.
class Book < ActiveRecord::Base
  def publish(publish_date)
    self.published = true
    if publish_date
      self.published_at = publish_date
    else
      self.published_at = Time.zone.now
    end
    save
  end
end
class BooksController < ApplicationController
  before_action :set_book, only: [:show, :edit, :update, :destroy, :publish]
  def publish
    pub_date = params[:publish_date]
    if @book.publish(pub_date)
else
end end
private
    def set_book
      @book = Book.find(params[:id])
    end
end
Fat model - Skinny controller: Hiện tại, Controller đang làm việc điều khiển, chuyển hướng, nên các thao tác tương tác db sẽ được đưa vào Model. Ta không thể để Controller xử lý các thao tác db, vậy nên, càng ngày, Model sẽ càng phình ra (Fat model), còn Controller thì chỉ có mấy dòng (Thin controller).
Xử lý logic trong view Ở trong view nếu chỉ có một ít logic đơn giản thì tạm chấp nhận được, nhưng nếu logic phức tạp quá thì thật sự không ổn.
9. Nested Resources/Routes
Sử dụng routes lồng nhau (nested routes) để thể hiện mối quan hệ của các model trong ActiveRecord. Ví dụ bạn có mô hình:
Post model has many comments Comment model belongs to Post Và trong config/routes.rb
resources :posts resources :comments
- http://localhost:3000/posts
 - http://localhost:3000/posts/1
 - http://localhost:3000/posts/1/edit
 - http://localhost:3000/comments
 - http://localhost:3000/comments/1
 - http://localhost:3000/comments/1/edit
 
Nó ok, nhưng không phải là cách tốt. Chúng ta cần sử dụng nested routes Comment lồng bên trong Post. Đây là cách làm:
resources :posts do resources :comments end
- http://localhost:3000/posts
 - http://localhost:3000/posts/1
 - http://localhost:3000/posts/1/edit
 - http://localhost:3000/posts/1/comments
 - http://localhost:3000/posts/1/comments/1
 - http://localhost:3000/posts/1/comments/1/edit
 
10. Don’t Put Too Much Logic in Views
Views là the presentation layer, không nên chưa logic. Bạn nên tránh việc kiểm tra như thế này:
<% if book.published? && book.published_at > 1.weeks.ago %>
  <span>Recently added</span>
<% end %>
//or
<% if current_user.roles.collect(&:name).include?("admin") || (user == book.owner && book.draft?) %>
  <%= link_to 'Delete', book, method: :delete, data: { confirm: 'Are you sure?' } %>
<% end %>
Bạn có thể di chuyển kiểm tra điều kiện này vào module helper(app/helpers). ví dụ:
module ApplicationHelper
  def recently_added?(book)
    book.published? && book.published_at > 1.weeks.ago
  end
  def can_delete?(book)
    current_user.roles.collect(&:name).include?("admin") || (user == book.owner && book.draft?)
  end
end
edit code view trên như sau:
<% if recently_added?(book) %>
  <span>Recently added</span>
<% end %>
//and
<% if can_delete?(book) %>
  <%= link_to 'Delete', book, method: :delete, data: { confirm: 'Are you sure?' } %>
<% end %>
Kết Luận
Đây là một sỗ Best practices của Rails. Hi vọng nó sẽ giúp được cho các bạn mới bắt đầu để làm việc tốt hơn với Rails.
Tham khảo https://www.sitepoint.com/10-ruby-on-rails-best-practices-3/