+2

ActiveStorage và ActiveRecord transactions

Active Storage là tình năng mới được giới thiệu từ Rails 5.2, tạo điều kiện cho việc tải lên tệp và liên kết của chúng với ActiveRecord.

Đó là một cơ chế tiện dụng và thuận tiện, nhưng trong quá trình sử dụng cũng sẽ có một số vấn đề.

Và vấn đề của mình gặp phải là việc xóa các tệp của ActiveStorage, sau một hồi tìm kiếm, thấy một bài viết có vẻ hay hay, nên mình xin phép chia sẽ lại trong bài viết này...

Giả sử chúng ta có model User đơn giản như sau

Class User < ApplicationRecord
  has_one_attached :avatar

  validates :first_name, last_name, presence: true, length: { maximum: 255 }
end

và chúng ta đang có một user với avatr dễ thương như này:

Mô hình của ActiveStorage như sau:

(Hình lụm ở bài viết gốc)

Bây giờ trong model User, chúng ta có thêm một hàm để cập nhật thông tin như sau (ví dụ thôi 😃 )

  def update_info! params
    ApplicationRecord.transaction do
      avatar.purge
      update! params
    end
  end

Nhìn có vẻ ổn, và mọi chuyện vẫn ok, nếu params truyền vào là hợp lệ

Tuy nhiên nếu param truyền vào ko đúng, ví dụ first_name = "" thì sao? Chắc là sẽ ROLLBACK và mọi thứ vẫn ok?

Dường như không có vấn đề gì, nhưng có một lỗi nghiêm trọng.

Trong đoạn mã trên, rollback xảy ra và các bản ghi active_storage_attachmentsactive_storage_blobs được khôi phục, nhưng tệp thực thể lại không được khôi phục.

[44] pry(main)> user.avatar.attached?
  ActiveStorage::Attachment Load (12.9ms)  SELECT  `active_storage_attachments`.* FROM `active_storage_attachments` WHERE `active_storage_attachments`.`record_id` = 106 AND `active_storage_attachments`.`record_type` = 'User' AND `active_storage_attachments`.`name` = 'avatar' LIMIT 1
  ActiveStorage::Blob Load (1.8ms)  SELECT  `active_storage_blobs`.* FROM `active_storage_blobs` WHERE `active_storage_blobs`.`id` = 356 LIMIT 1
=> true
[45] pry(main)> user.avatar.download
  Disk Storage (1.5ms) Downloaded file from key: evTpR7oo8dHPrLoxjFacp6oM
Errno::ENOENT: No such file or directory @ rb_sysopen - /home/le.duc.tien/JCC/bps/storage/ev/Tp/evTpR7oo8dHPrLoxjFacp6oM

Và em user dễ thương lúc trước thành ra thế này

Và đây là cách nó hoạt động

ApplicationRecord.transaction không thể được khôi phục tệp trình xóa xảy ra bên ngoài giao dịch RDB như đĩa cục bộ hoặc S3.

Vậy cách giải quyết là gì?

Đơn giản là thay đổi 1 tí ở method update_info!

  def update_info! params
    ApplicationRecord.transaction do
      update! params
      avatar.purge
    end
  end

có vẻ ổn, tuy nhiên, nếu avatar cũng bị validate thì việc đổi thứ tự giữa save! và purge sẽ không có ý nghĩa

  def validate_avatar
    errors.add(:avatar, :presence) if avatar.blank?
  end

Vậy cách giải quyết là gì?

Sử dụng detach thay vì purge

detach chỉ loại bỏ active_storage_attachments

attached? sẽ trả về false nếu không có bản ghi active_storage_attachments

Tuy nhiên, như bạn có thể thấy trong hình trên, sử dụng detach active_storage_attachments và tệp thực thể mà nó đề cập đến có thể gây lãng phí tài nguyên đĩa, vì vậy nó cần được làm sạch thường xuyên.

Đây là code bạn có thể sử dụng:

ActiveStorage::Blob.unattached.find_each(&:purge)

Tham khảo: https://tech.smarthr.jp/entry/2018/09/14/130139


Mr.Nara


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí