Error Handling in Rails
Theo như Luật Murphy, bất cứ điều gì nếu bắt đầu sai thì sẽ kéo theo sai cả quá trình, đó là lý do tại sao công tác chuẩn bị lại quan trọng. Nó áp dụng ở khắp mọi nơi, ngay cả trong phát triển phần mềm. Các ứng dụng mà chúng tôi phát triển phải đủ mạnh mẽ để xử lý nó. Nói cách khác, nó phải có độ ...
Theo như Luật Murphy, bất cứ điều gì nếu bắt đầu sai thì sẽ kéo theo sai cả quá trình, đó là lý do tại sao công tác chuẩn bị lại quan trọng. Nó áp dụng ở khắp mọi nơi, ngay cả trong phát triển phần mềm.
Các ứng dụng mà chúng tôi phát triển phải đủ mạnh mẽ để xử lý nó. Nói cách khác, nó phải có độ đàn hồi hay tính mềm dẻo. Đây chính là lý do tôi viết bài về Error Handling in Rails trong bài này.
Trong Ruby on Rails khi chúng ta xử lý các sai sót ở cấp độ điều khiển, ta thường sử dụng if .. else hoặc when ... case ... ví dụ như:
class UsersController < ApplicationController def show @user = User.find(params[:id]) if @user render json: @user, status: :ok else render json: { error: "User with id #{params[:id]} not found." }, status: :not_found end end end
Xét ở ví dụ trên ta thấy, nếu đối tượng User được tìm thấy bản ghi ta trả về json đối tượng đó, nếu không tìm thấy trả về lỗi. Đó là phương pháp hay được chúng ta sử dụng trong việc điều hướng lỗi không mong muốn.
Khi không tìm thấy bản ghi nào trong đối tượng User, nó sẽ chuyển hướng đến lỗi 500 là lỗi dự phòng trong Ruby on Rails hay bất cứ ngôn ngữ lập trình nào mà tôi và các bạn đã biết. Lỗi này sảy ra đối tượng rỗng nó sẽ được điều hướng đến đối tượng RecordNotFound để bắn ra lỗi. Điều này xảy ra khi bạn sử dụng find_by! như ví dụ trên hoặc phương thức tìm kiếm khác.
Trước khi đi vào sửa các lỗi một điều quan trọng chúng ta cần là hiểu rõ loại lỗi đó gây ra. Như trong ví dụ lỗi ActiveRecord::RecordNotFound bên dưới:
begin @user = User.find_by!(id: 1) rescue ActiveRecord::RecordNotFound => e print e end
Nhưng khi bạn muốn sử dụng tất cả các Exceptions thì điều quan trọng bạn cần biết chính là sự khác nhau giữa Exception và Error trong Rails. Như vậy cách tốt nhất là giữ nguyên dạng nguyên bản của Exception nếu không muốn dùng ActiveRecord::RecordNotFound, ví dụ:
begin @user = User.find_by!(id: 1) rescue Exception => e # Never do this! print e end
Hoặc bạn cũng có thể sử dụng StandardError để xử lý ngoại lệ thay cho Exception cũng được, như ví dụ:
begin @user = User.find_by!(id: 1) rescue StandardError => e print e end
Để giải quyết các error chúng ta có thể sử dụng khối Rescue. Khối Rescue trong Ruby on Rails xử lý tương tự như khối try ... catch trong Java hoặc PHP... vậy. ví dụ:
class UsersController < ApplicationController def show @user = User.find(params[:id]) render json: @user, status: :ok rescue ActiveRecord::RecordNotFound => e render json: { error: e.to_s }, status: :not_found end end
Với phương pháp này các error được xử lý trong Controller. Mặc dù cách xử lý này hoàn toàn có thể không được giải quyết tốt nhất để xử lý lỗi.
Với các can thiệp và xử lý lỗi như trên, chúng ta có thể dễ dàng khỏi lỗi do người dùng gây ra khi cố tình truyền vào param không đúng. Mặc dù cách này khá hiêu quả nhưng đây chưa phải là cách tốt nhất để xử lý các lỗi.
Để xử lý các error lựa chọn đầu tiên của tôi là viết ở dưới ApplicationController. Đó là cách tốt nhất để tách xử lý error hoặc Exception ra khỏi logic của dựa án.
Tạo module để xử lý error và Exception ở mức độ toàn cục. Ta tạo một module ErrorHandler trong file error_handler.rb để xử lý error và để nó trong lib/errors sau đó bạn include vào ApplicationController, code ví dụ như bên dưới:
# application_controller.rb class ApplicationController < ActionController::Base protect_from_forgery with: :exception include Error::ErrorHandler end
#error_handler.rb module Error module ErrorHandler def self.included(clazz) clazz.class_eval do rescue_from ActiveRecord::RecordNotFound, with: :record_not_found end end private def record_not_found(_e) json = Helpers::Render.json(:record_not_found, _e.to_s) render json: json, status: 404 end end end
Lưu ý: Ở đây tối sử dụng Helper classes để render đầu ra dưới dạng json.
Chúng ta sẽ include tất cả các module ErrorHandler trong ApplicationController.
#users_controller.rb # After including ErrorHandler module in ApplicationController # Remove the Error block from the controller actions. class UsersController < ApplicationController def show @user = User.find_by!(id: params[:id]) render json: @user, status: :ok end end
Chúng ta sẽ refactor module ErrorModule để điều hướng và xử lý các lỗi có thể gặp phải.
# error_handler.rb module Error module ErrorHandler def self.included(clazz) clazz.class_eval do rescue_from ActiveRecord::RecordNotFound do |e| respond(:record_not_found, 404, e.to_s) end rescue_from StandardError do |e| respond(:standard_error, 500, e.to_s) end end end private def respond(_error, _status, _message) json = Helpers::Render.json(_error, _status, _message) render json: json end end end
Nếu lỗi của bạn nhận được thông báo ActiveRecord:RecordNotFound nó được kế thừa StandardError lỗi không có bản ghi nào. Chúng ta có thể xử lý lỗi này thông qua :record_not_found. StandardError là khối lỗi dự phòng để xử lý tất cả các lỗi gặp phải trong ứng dụng.
Bạn có thể xử lý các trường hợp ngoại lệ thông thường như 404 và 500 thông quá ngoại lệ defult trong ứng dụng Ruby on Rails. Bạn cũng có thể tạo ra điểu hướng ngoại lệ riêng của mình, có thể tạo đa điều hướng ngoại lệ như bên dưới tôi viết:
# errors_controller.rb class ErrorsController < ApplicationController def not_found render json: { status: 404, error: :not_found, message: 'Not Found.' }, status: 404 end def internal_server_error render json: { status: 500, error: :internal_server_error, message: 'Internal Server Error' }, status: 500 end end
Rails sử dụng Routes để xử lý trường hợp ngoại lệ, ta chỉ cần thêm vào application.rb các đoạn mã như sau:
Rails.application.routes.draw do get '/404', to: 'errors#not_found' get '/500', to: 'errors#internal_server_error' root 'home#index' resources :users, only: [:create, :show] get 'not_visible', to: 'home#not_visible' end
Việc sử dụng mềm dẻo các điều hướng exceptions và errors giúp cho ứng dụng của chúng ta khi hoạt động sẽ trơn tru và mượt mà hơn.
Có rất nhiều cách để bạn có thể custom điều hướng lỗi, trên đây là tôi chỉ giới thiệu vài cách cơ bản. Các bạn có thể tham khảo thêm ở các bài viết khác hoặc Google Search.
Cảm ơn các bạn đã đọc bài viết, có thắc mắc hay ý kiến gì xin để lại comment phía dưới, chúc các bạn vui vẻ! (thankyou).
- Error Rails
- Rescue StandardError, Not Exception