Callback hợp lý khi việc cần làm là thuộc tính nội tại của bản ghi và phải đúng trong mọi ngữ cảnh: chuẩn hoá dữ liệu (before_save { self.email = email.downcase }), sinh slug, dọn file đính kèm khi destroy.
Nên tránh khi callback gây tác dụng phụ ra bên ngoài:
- Gửi email, gọi API bên thứ ba, enqueue job, ghi log nghiệp vụ.
- Sửa model khác, tạo bản ghi liên quan.
- Logic chỉ đúng cho một luồng (đăng ký qua web) nhưng sai cho luồng khác (import CSV, seed, sửa trong console, factory trong test).
Lý do: callback chạy ngầm với mọi thao tác save, nên import 10.000 dòng sẽ vô tình gửi 10.000 email; test phải stub khắp nơi; và nếu callback gọi API mà đang nằm trong transaction thì transaction bị giữ mở suốt thời gian chờ mạng.
# instead of after_create :send_welcome_email in the model
class RegisterUser
def call(params)
user = User.create!(params)
WelcomeMailer.welcome(user).deliver_later
user
end
endChốt: đưa tác dụng phụ ra service object (hoặc controller action) để nó tường minh và chỉ chạy đúng luồng cần.
Nếu buộc phải dùng callback kèm side effect, dùng after_commit để tránh phát tín hiệu khi transaction còn có thể rollback.