Callback trong Rails để làm gì
Bài đăng này đã không được cập nhật trong 7 năm
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
All rights reserved