Rails – Callback

3.5/5 - (8 votes)

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.

0 0 votes
Article Rating
Subscribe
Thông báo cho tôi qua email khi
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

6 Comments
Inline Feedbacks
View all comments
Kiều Thị Ánh
Kiều Thị Ánh
6 năm trước

Em xin lỗi ad, vì hỏi không đúng chủ đề ạ.
Nhưng mà em làm mãi mà không được nên muốn hỏi ad, mong ad giúp em ạ.
Em có table room và table room_type
model của room.rb

class Room < ApplicationRecord
belongs_to :room_type
end

class Room < ApplicationRecord
has_many :rooms
end

Bây giờ em muốn tìm phòng theo loại phòng, em có file : filter_form:

<div class="form-group">
      <%= label_tag t(".room_directory") %>
      <%= text_field_tag :number_or_room_types_name_cont,
        params[:number_or_room_types_name_cont],
        class: "form-control" %>
    </div>

Nhưng lúc em tìm thì lại k ra kết quả ạ.
Em cảm ơn ad nhiều ạ.

Kiều Thị Ánh
Kiều Thị Ánh
6 năm trước

Em xin lỗi ad, vì hỏi không đúng chủ đề ạ.
Nhưng mà em làm mãi mà không được nên muốn hỏi ad, mong ad giúp em ạ.
Em có table room và table room_type
model của room.rb

class Room < ApplicationRecord
  belongs_to :room_type
end
class Room < ApplicationRecord
has_many :rooms
end

Bây giờ em muốn tìm phòng theo loại phòng, em có file : filter_form:

<div class="form-group">
      <%= label_tag t(".room_directory") %>
      <%= text_field_tag :number_or_room_types_name_cont,
        params[:number_or_room_types_name_cont],
        class: "form-control" %>
    </div>

Nhưng lúc em tìm thì lại k ra kết quả ạ.
Em cảm ơn ad nhiều ạ.

Kiều Thị Ánh
Kiều Thị Ánh
6 năm trước
Reply to  Phở Code

Dạ, vâng ạ.

rooms_controller.rb
def index
@q = Room.ransack(params[:q])
		@supports = Supports::RoomSupport.new room: @room
		@rooms = Room.includes(:room_type).distinct
			.price_between(params[:from_price], params[:to_price]).price_order(params[:price_rule])
			.search(search_params).result
			.paginate page: params[:page],
			per_page: Settings.rooms.rooms_per_page
end

def search_params
   params.permit :number_or_room_types_name_cont,
       :status_eq, :max_people_gteq
end
Kiều Thị Ánh
Kiều Thị Ánh
6 năm trước
Reply to  Phở Code

Dạ không báo lỗi ạ, trên url có hiển thị đúng theo tên loại phòng nhưng lại không hiển thị kết quả trên giao diện web ạ.
http://localhost:3000/admin/rooms?utf8=%E2%9C%93&number_or_room_types_name_cont=Luxury&price_rule=&room_types_name_cont=1&max_people_gteq=&status_eq=&from_price=&to_price=&commit=T%C3%ACm+ki%E1%BA%BFm