Rails mistakes
Bài đăng này đã không được cập nhật trong 5 năm
Mở đầu
rails là một web framework phổ biến, dựa trên nền ngôn ngữ ruby giúp việc phát triển web trở nên đơn giản và dễ dàng.
rails được xây dựng dựa trên nguyên tắc convention over configuration
, hiểu đơn giản rằng mọi thứ sẽ hoạt động auto-magically mà không cần nhiều code nếu ta tuân theo những convention chuẩn của rails (như naming, code structure, ...)
Bởi vì rails magic mà mọi thứ được làm một cách tự động và được che dấu (magic?), đôi khi sẽ có những lỗi phát sinh mà ta không thể hiểu rõ nếu không biết rõ cách rails magic hoạt động. rails dễ sử dụng, nhưng đồng thời cũng dễ dàng sử dụng sai chúng.
Dưới đây ta sẽ tìm hiểu những sai lầm hay mắc phải khi làm việc với rails
Đưa quá nhiều logic vào trong controller
Ta có thể đưa các view logic, model logic trong controller, khi đó sẽ vi phạm nguyên tắc single responsibility , khi mà nó có thể dẫn tới code khó hiểu và dễ phát sinh lỗi.
Chỉ nên đưa những logic sau:
- Session and cookie handling: bao gồm các logic xử lý đăng nhập/đăng ký, phân quyền hay làm việc với cookie
- Request parameter management: lấy request params
- Model selection: tìm model object ứng với request params nhận được
- Rendering/redirecting. Render/redirect kết quả (html, xml, json, etc.) phù hợp
Đưa quá nhiều logic vào trong view
Trong rails sử dụng erb template để tạo các file html view với ruby code embed.
Nếu sử dụng không cẩn thận có thể dẫn đến những file khó để quản lý và bảo trì. Cũng có thể dẫn đến vi phạm quy tắc DRY (Don't repeat yourself) với quá nhiều phần bị lặp lại
Lấy ví dụ, giả sử ta có method current_user trả về user hiện tại, logic của view file có thể sẽ như sau:
<h3>
Welcome,
<% if current_user %>
<%= current_user.name %>
<% else %>
Guest
<% end %>
</h3>
Một cách để handle là để method current_user luôn trả về 1 object khác nil:
def current_user
@current_user ||= User.find session[:user_id] if session[:user_id]
if @current_user
@current_user
else
OpenStruct.new(name: 'Guest')
end
end
Do đó ta có thể thay đổi logic view:
<h3>Welcome, <%= current_user.name %></h3>
Ngoài ra nên sử dụng layout và partial để render những code trùng lặp
Đưa quá nhiều logic trong model
Những chức năng như tạo email thông báo, tương tác với external service, convert data format không phải là việc có thể đưa vào ActiveRecord model (nơi chỉ làm những thứ như truy xuất, ghi các bản ghi vào database)
Vậy logic không nên để trong controller, view hay model thì nó nên để ở đâu?
Sử dụng plain old Ruby object
(POROs) tạo những class bên ngoài rails, ta có thể đưa các chức năng trên vào class này, thay vì đưa chúng trong model
Những login nên đưa vào model:
- Configuration: các marco quan hệ, validations
- Access wrappers: method che dấu thông tin (ví dụ method
full_nametrả về họ tên đầy đủ từ 2 trườngfirst_namevàlast_nametrong model) - Sophisticated queries: có thể define class method qua scope (ví dụ scope :active ->{where(active: true)}), không nên sử dụng
wherebên ngoài model
Sử dụng nhiều gem
rails được hỗ trợ bởi rich ecosystem of gems
giúp việc xây dựng những hệ thống phức tập một cách nhanh chóng. Tuy nhiên không nên lạm dụng quá nhiều vào nó.
Sử dụng nhiều gem làm tăng kích thước của Rails process, làm chậm hiệu suất ứng dụng, tăng chi phí vận hành
Hơn nữa, mỗi gem được đưa vào ứng dụng có thể cần cài đặt thêm những gem khác, ví dụ thêm gem rails_admin sẽ phải thêm 11 gem nữa vào ứng dụng.
Bỏ qua log files
Chúng ta thường không chú ý tới những dòng log trên server. Để ý tới chúng sẽ dễ dàng hơn trong việc debug khi chạy ứng dụng
Lấy vi dụ, ta định nghĩa những quan hệ trong database ở model, những method trong model cần tới những truy vấn tới database. Nếu ta để tới log file thì dễ dàng nhìn ra những dòng truy vấn SQL thường xuyên được thực hiện.
Giả sử ta muốn kiểm tra N+1 problem, điều này có thể thưc hiện dễ dàng khi check log file
Ví dụ, ta có Posts has_many Comments, method comments_for_top_three_posts đưa ra tất cả comments của 3 posts:
def comments_for_top_three_posts
posts = Post.limit(3)
posts.flat_map do |post|
post.comments.to_a
end
end
Check log file khi có request gọi method này:
Processing by PostsController#some_comments as HTML
Post Load (0.4ms) SELECT "posts".* FROM "posts" LIMIT 3
Comment Load (5.6ms) ELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 1]]
Comment Load (0.4ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 2]]
Comment Load (1.5ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 3]]
Rendered posts/some_comments.html.erb within layouts/application (12.5ms)
Dễ thấy có 4 SQL query được thực hiện ( 1 để lấy 3 posts, 3 còn lại lấy từng comments của từng posts )
Eager loading giúp giảm thiểu số query:
def comments_for_top_three_posts
posts = Post.includes(:comments).limit(3)
posts.flat_map do |post|
post.comments.to_a
end
end
Và khi check log file:
Processing by PostsController#some_comments as HTML
Post Load (0.5ms) SELECT "posts".* FROM "posts" LIMIT 3
Comment Load (4.4ms) SELECT "comments".* FROM "comments" WHERE"comments "."post_id" IN (1, 2, 3)
Rendered posts/some_comments.html.erb within layouts/application (12.2ms)
Ở đây chỉ có 2 query được thực hiện (Các bạn có thể tìm hiểu thêm về Eager loading .)
Hi vọng với những ví dụ trên có thể giúp các bạn tránh được những sai lầm khi làm việc với rails, cảm ơn mọi người đã đọc bài viết của mình 
All rights reserved
