Callback trong Rails hoạt động như thế nào?
Bài đăng này đã không được cập nhật trong 6 năm
Với bất cứ lập trình viên nào hẳn từ khóa Callback cũng đã quá quen thuộc, nó xuất hiện ở gần như mọi ngôn ngữ lập trình, và với Rails cũng vậy, khi bạn sử dụng các phương thức như before_create
, after_save
, ... chính là đang sử dụng callback trong ứng dụng của mình. Tuy nhiên không phải ai cũng hiểu rõ được rằng các hàm callback này được tạo ra như thế nào?, Khi nào thì callback được gọi đến?, Có những loại callback nào?, ... thì trong bài này chúng ta sẽ cùng đi làm rõ từng vấn đề một nhé.
1. Callback là gì ?
Rất đơn giản đúng như tên gọi của nó Callback nghĩa là gọi lại, Callback cho phép bạn thực thi các phương thức đã khai báo trước đó một cách tự động trước khi (hoặc sau khi, hoặc cả trước và sau khi) một đoạn code khác trong chương trình được chạy. Một số callback phổ biến của Active Record được sử dụng before_validation
, after_validation
, before_save
, around_save
, before_create
, around_create
,... còn rất nhiều các phương thức khác nữa có thể xem tại Active Record Callback
2. Khi nào cần sử dụng Callback.
Cách tốt nhất và nhanh nhất để hiểu một vấn đề đó là xem xét một ví dụ: trong EventsController
đang xử lý việc sửa và xóa một sự kiện, có 2 action là update
và destroy
class EventsController < ApplicationController
def update
@event = current_user.events.find_by id: params[:id]
if @event.update_attributes event_params
// Do somethings
else
// Do somethings
end
end
def destroy
@event = current_user.events.find_by id: params[:id]
if @event.destroy
// Do somethings
else
// Do somethings
end
end
end
Cả 2 action ta thấy đều phải thực hiện cùng một công việc trước khi update hoặc xóa đi là tìm sự kiện đó trong bảng events
dựa vào params id được truyền vào sau đó gán sự kiện tìm được cho biến @event
. Thay vì việc bị trùng lặp code như vậy ta có thể xử lý như sau với Callback:
class EventsController < ApplicationController
before_action :find_event, only: [:update, :destroy]
def update
if @event.update_attributes event_params
// Do somethings
else
// Do somethings
end
end
def destroy
if @event.destroy
// Do somethings
else
// Do somethings
end
end
private
def find_event
@event = current_user.events.find_by id: params[:id]
end
end
Với đoạn code trên ta đã thực hiện một số thao tác:
- Khai báo phương thức
find_event
để tìm ra sự kiện dựa vào param id truyền vào. - Dùng callback
before_action
gọi đếnfind_event
đã được khai báo, và chỉ callback đối với 2 phương thức làupdate
vàdestroy
. - So với code cũ có thể dài hơn nhưng nhìn rất gọn gàng và clean, chương trình cũng rất dễ để sửa đổi hoặc fix lỗi, nếu như có vấn đề với việc tìm event ta chỉ cần xem xét và sửa trong hàm
find_event
thay vì phải đi sửa ở tất cả các action như trước.
3. Cơ chế hoạt động của callback trong Rails
3.1. ActiveSupport là gì ?
Đến đây, chúng ta sẽ cùng xem xét kĩ càng hơn trong source code của Rails để hiểu cặn kẽ về Callback.
- Các phương thức callback trong Active Record không tự ngẫu nhiên mà có, hay các callback trong ActionPack cũng vậy, hãy thử hình dung giả sử tất cả các module trong rails đều cung cấp các phương thức callback hoặc khi ta muốn tự tạo các callback riêng cho mình thì một câu hỏi lớn được đặt ra là Nên bằt đầu từ đâu để tạo cho mình một Callback?, bản thân Active Record hay ActionPack cũng vậy, nó cũng đã tự hỏi chính mình là tôi phải đi đâu để tìm các công cụ xây nên những Callback cho chính mình. Câu trả lời chính là ActiveSupport, đây là một module trong Rails cung cấp các công cụ cần thiết nhất cho phép các module khác sau khi include ActiveSupport có thể tạo callback riêng cho nó.
- Trong ActiveSupport khai báo 3 phương thức quan trọng và là cốt lõi nhất cho phép tạo nên một callback đó là:
define_callbacks
: Định nghĩa ra các sự kiện trong một chu kì hoạt động của đối tượng sẽ được hỗ trợ callback, vdu nhưsave
,destroy
,update
, ... là các sự kiện khá phổ biến, vớidefine_callback
ta có thể tự custom một callback cho riêng mình. (Hàmdefine_callback
)set_callback
: Thiết lập các instance method hoặc proc, ... để sẵn sàng được gọi lại, có thể hiểu là install các phương thức đã khai báo trước đó và sẵn sàng đợi cho đến khi được callback. ( Hàmset_callback
)run_callback
: Chạy các phương thức đã được install trước đó bởiset_callback
vào một thời điểm thích hợp. ( Hàmrun_callback
)
- Có 3 loại callback được hỗ trợ đó là
before
,after
vàaround
.before
callback chạy trước một sự kiện,after
callback chạy sau khi sự kiện xảy ra vàaround
callback chạy cả trước và sau khi sự kiện xảy ra.
3.2. ActiveRecord dùng ActiveSupport để tạo Callback như thế nào?
Tới đây ta đã biết được công cụ cho phép các module khác tạo ra Callback đó là ActiveSupport. Nhưng có vẻ vẫn còn mơ hồ nên hãy cùng xem xét cụ thể hơn nữa các Callback của active record được tạo ra như thế nào? Đầu tiên, xem xét ActiveRecord::Callbacks module:
module Callbacks
extend ActiveSupport::Concern
CALLBACKS = [
:after_initialize, :after_find, :after_touch, :before_validation, :after_validation,
:before_save, :around_save, :after_save, :before_create, :around_create,
:after_create, :before_update, :around_update, :after_update,
:before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback
]
included do
extend ActiveModel::Callbacks
include ActiveModel::Validations::Callbacks
define_model_callbacks :initialize, :find, :touch, :only => :after
define_model_callbacks :save, :create, :update, :destroy
end
def destroy #:nodoc:
run_callbacks(:destroy) { super }
end
def touch(*) #:nodoc:
run_callbacks(:touch) { super }
end
private
def create_or_update #:nodoc:
run_callbacks(:save) { super }
end
def create #:nodoc:
run_callbacks(:create) { super }
end
def update(*) #:nodoc:
run_callbacks(:update) { super }
end
end
end
- Có thể thấy phương thức
define_model_callbacks
truyền vào các tham sốinitialize, :find, :touch, :save, :create, :update, :destroy
, đây có vẻ là nơi các callback được tạo ra. - Khai báo các phương thức
create
,update
,destroy
, ... mỗi phương thức đều gọi đếnrun_callback
có nghĩa là mỗi khi các phương thức này được kích hoạt thì nó sẽ gọi đến callback trước, chạy xong callback mới chạy đến các lệnh bên trong. Tiếp theo, xem xét kĩ hơn một chút bên trongdefine_model_callbacks
có gì:
def define_model_callbacks(*callbacks)
options = callbacks.extract_options!
options = {
:terminator => "result == false",
:scope => [:kind, :name],
:only => [:before, :around, :after]
}.merge(options)
types = Array.wrap(options.delete(:only))
callbacks.each do |callback|
define_callbacks(callback, options)
types.each do |type|
send("_define_#{type}_model_callback", self, callback)
end
end
end
def _define_before_model_callback(klass, callback) #:nodoc:
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
def self.before_#{callback}(*args, &block)
set_callback(:#{callback}, :before, *args, &block)
end
CALLBACK
end
def _define_around_model_callback(klass, callback) #:nodoc:
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
def self.around_#{callback}(*args, &block)
set_callback(:#{callback}, :around, *args, &block)
end
CALLBACK
end
def _define_after_model_callback(klass, callback) #:nodoc:
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
def self.after_#{callback}(*args, &block)
options = args.extract_options!
options[:prepend] = true
options[:if] = Array.wrap(options[:if]) << "!halted && value != false"
set_callback(:#{callback}, :after, *(args << options), &block)
end
CALLBACK
end
end
end
- Ta hãy chú ý đến
callbacks.each do |callback|
define_callbacks(callback, options)
types.each do |type|
send("_define_#{type}_model_callback", self, callback)
end
end
callbacks
ở đây là một mảng các phương thức được truyển vào hàmdefine_model_callbacks
, cụ thể làinitialize
,find
,touch
,save
,create
,update
,destroy
. Tại đâydefine_callback
sẽ lần lượt định nghĩa từng phương thức này sẽ là những phương thức được hỗ trợ callback- Còn cụ thể hàm callback mà ta vẫn thường sử dụng được tạo ra bởi
send("_define_#{type}_model_callback", self, callback)
,type
bao gồmbefore
,after
vàaround
. Lấy vị dụ vớisave
, sau khi được define bởi hàmdefine_callback
, sẽ có 3 phương thức tương ứng với 3 types được tạo ra chosave
def _define_before_model_callback(klass, callback) #:nodoc:
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
def self.before_#{callback}(*args, &block)
set_callback(:#{callback}, :before, *args, &block)
end
CALLBACK
end
def _define_around_model_callback(klass, callback) #:nodoc:
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
def self.around_#{callback}(*args, &block)
set_callback(:#{callback}, :around, *args, &block)
end
CALLBACK
end
def _define_after_model_callback(klass, callback) #:nodoc:
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
def self.after_#{callback}(*args, &block)
options = args.extract_options!
options[:prepend] = true
options[:if] = Array.wrap(options[:if]) << "!halted && value != false"
set_callback(:#{callback}, :after, *(args << options), &block)
end
CALLBACK
end
- Lần lượt 3 hàm trên sẽ tạo ra 3 phương thức
before_save
,around_save
vàafter_save
, để ý bên trong mỗi hàm trên còn có gọi đếnset_callback
, điều này có nghĩa là phương thức bạn truyền vàobefore_save
,around_save
vàafter_save
chính là truyền vào cho hàmset_callback
này. - Trên đây là toàn bộ vòng đời của một callback, được tạo ra như thế nào, thiết lập các phương thức ra sao, khi nào thì chạy, hi vọng qua bài viết này mọi người sẽ có một cái nhìn rõ hơn về callback trong Rails.
4. Tài liệu tham khảo
All rights reserved