Trong quá trình chạy ứng dụng, một đối tượng có thể được tạo ra, được cập nhật hoặc bị hủy thông qua các thao tác CRUD. Rails cung cấp cơ chế callback để chúng ta có thể kiểm soát trạng thái của các đối tượng này.
Callback là các phương thức/hàm được gọi trước hoặc sau khi có sự thay đổi trạng thái (như tạo, lưu, xóa, cập nhật, validate…) của đối tượng.
Ví dụ
Chúng ta sẽ không cho thực hiện chức năng xóa user nếu trong bảng chỉ còn lại một user.
Đầu tiên chúng ta sửa lại file layout một tí như sau:
<!DOCTYPE html> <html> <head> <title>Books Store</title> <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> <%= csrf_meta_tags %> </head> <body id="store"> <div id="banner"> <%= image_tag("logo.png") %> <%= @page_title || "Books Store" %> </div> <div id="columns"> <div id="side"> <div id="cart"> <%= hide_cart_if(current_cart.line_items.empty?, :id => "cart") do %> <%= render current_cart %> <% end %> </div> <a href="#">Home</a><br /> <a href="#">FAQ</a><br /> <a href="#">News</a><br /> <a href="#">Contact</a><br /> <% if session[:user_id] %> <br/> <%= link_to 'Orders', '/orders' %><br /> <%= link_to 'Products', '/products' %><br /> <%= link_to 'Users', '/users' %><br /> <%= button_to 'Logout', '/logout', :method => :delete %> <br/> <% else %> <br/> <%= link_to 'Log In', login_path %><br /> <% end %> </div> <div id="main"> <%= yield %> </div> </div> </body> </html>
Chúng ta tạo thêm mấy nút bấm dẫn đến trang /products, /users, /orders, /logout
nếu người dùng đã đăng nhập, nếu chưa thì hiển thị nút dẫn đến trang /login
.
Tiếp theo chúng ta sửa lại lớp User
như sau:
require 'digest/sha2' class User < ActiveRecord::Base validates :name, :presence => true, :uniqueness => true validates :password, :confirmation => true attr_accessor :password_confirmation attr_reader :password validate :password_must_be_present def User.encrypt_password(password, salt) Digest::SHA2.hexdigest(password + salt) end def password=(password) @password = password if password.present? generate_salt self.hashed_password = self.class.encrypt_password(password, salt) end end def User.authenticate(name, password) if user = find_by_name(name) puts encrypt_password(password, user.salt) if user.hashed_password == encrypt_password(password, user.salt) user end end end after_destroy :check_user_empty def check_user_empty if User.count.zero? raise "Can't delete last user" end end private def password_must_be_present if hashed_password.present? == false errors.add(:password, "Missing password") end end def generate_salt self.salt = self.object_id.to_s + rand.to_s end end
Chúng ta định nghĩa phương thức check_user_empty,
phương thức này kiểm tra xem trong bảng User
có rỗng hay không, nếu rỗng thì giải phóng một lỗi exception.
Sau đó ở trên chúng ta gọi phương thức after_destroy :check_user_empty.
Phương thức after_destroy
là một phương thức callback, phương thức này sẽ gọi phương thức :check_user_empty
mỗi khi có một thao tác nào đó liên quan đến câu lệnh DELETE
trong cơ sở dữ liệu xảy ra. Tức là ở đây nếu người dùng bấm nút ‘Destroy’ để xóa user thì phương thức check_user_exist
sẽ được gọi, và nếu bảng users
trong CSDL không còn bản ghi nào thì một lỗi exception sẽ được sinh ra.
Và nếu sau lời gọi hàm callback mà có lỗi exception nào đó thì lỗi này sẽ được gửi ngược về nơi đã gọi ra nó, tức là ở đây tương ứng với lời gọi @user.destroy
trong phương thức destroy
của lớp UsersController
. Ngoài ra lỗi exception cũng sẽ bắt buộc Rails phải “đảo ngược” câu truy vấn vừa thực hiện, tức là nếu xóa user mà có lỗi exception đó thì user đó sẽ được phục hồi nguyên vẹn trong CSDL.
Bây giờ chúng ta sửa lại phương thức destroy
trong lớp UsersController
để bắt exception như sau:
class UsersController < ApplicationController . . . # DELETE /users/1 # DELETE /users/1.json def destroy begin @user.destroy flash[:notice] = "User #{@user.name} deleted" rescue Exception => e flash[:notice] = e.message end respond_to do |format| format.html { redirect_to users_url } format.json { head :no_content } end end . . . end
Nếu có lỗi exception xảy ra thì chúng ta chỉ đơn giản là thêm câu thông báo vào biến flash
.
Nếu bạn muốn Rails chỉ thực hiện phục hồi dữ liệu chứ không muốn tạo một đối tượng exception nào thì trong lớp User
, chúng ta cho giải phóng một đối tượng ActiveRecord::Rollback
là được.
Ngoài ra còn có rất nhiều phương thức callback khác như:
CREATE
before_validation
after_validation
before_save
around_save
before_create
around_create
after_create
after_save
after_commit/after_rollback
UPDATE
before_validation
after_validation
before_save
around_save
before_update
around_update
after_update
after_save
after_commit/after_rollback
DELETE
before_destroy
around_destroy
after_destroy
after_commit/after_rollback
Bạn có thể tìm hiểu thêm tại đây.