10 Ruby on Rails Best Practices

logo.png

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ụ:

Screenshot from 2016-11-26 10-22-49.png

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

or

    if name != "sarmad"
      do_that
    end

Good

    unless true
      do_this
    end

or

    unless name == "sarmad"
      do_that
    end

Bad

    unless user.save

throw error

    else

return success

    end

Good

    if user.save

return success

    else

throw error

    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

or

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]

code omitted for brevity

  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

success response, some redirect with a flash notice

    else

failure response, some redirect with a flash alert

    end
  end

code omitted for brevity

  private

Use callbacks to share common setup or constraints between actions

    def set_book
      @book = Book.find(params[:id])
    end

code omitted for brevity

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]

code omitted for brevity

  def publish
    pub_date = params[:publish_date]
    if @book.publish(pub_date)

success response, some redirect with a flash notice

    else

failure response, some redirect with a flash alert

    end
  end

code omitted for brevity

  private

Use callbacks to share common setup or constraints between actions

    def set_book
      @book = Book.find(params[:id])
    end

code omitted for brevity

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

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

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ụ:

app/view/helpers/application_helper.rb

module ApplicationHelper
  def recently_added?(book)
    book.published? && book.published_at > 1.weeks.ago
  end

current_user is defined in application controller, which can be

accessed from helper modules & methods

  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/