Callback trong Rails để làm gì
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ư ...
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:
app/views/layout/application.html.erb
<!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:
app/models/user.rb
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: app/controllers/users_controller.rb
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