Tìm hiểu về Soft delete với gem Paranoia trong Ruby on Rails

Paranoia dùng để làm gì?

Gem này có tác dụng khi xóa bỏ một đối tượng, nó sẽ không thực sự xóa bản ghi ở trong cơ sở dữ liệu, mà chỉ ẩn nó đi.

Paranoia thực hiện điều này bằng cách đặt trường deleted_at thành thời điểm hiện tại khi bạn xóa bản ghi và ẩn nó bằng cách phân tích tất cả các truy vấn trên model của bạn và chỉ hiện các bản ghi không có trường deleted_at hoặc không có giá trị.

Nếu bạn muốn thực sự xóa một đối tượng, bạn có thể gọi really_destroy! Điều này cũng sẽ thực sự xóa tất các các association dependent: :destroy, điều này khá nguy hiểm các bạn hãy cân nhắc trước khi sử dụng

Cài đặt & sử dụng

Cài đặt

Để cài đặt gem paranoia, ta thêm tên gem vào Gemfile:

Với Rails 3:
  gem "paranoia", "~> 1.0"
Với Rails 4:
  gem "paranoia", "~> 2.0"
Với Rails 5:
  gem "paranoia", "~> 2.2.0.pre"

Sau đó chạy bundle ínstall để cài đặt.

Chạy migrate để thêm column deleted_at vào table:

bin/rails generate migration AddDeletedAtToClient deleted_at:datetime:index
# Client là tên table cần soft delete

Sử dụng

Thêm acts_as_paranoid vào model để thực hiện soft delete với ActiveRecord

class Client < ActiveRecord::Base
  acts_as_paranoid

  # ...
end

Nếu bạn muốn sử dụng một cột không phải là remove_at, bạn có thể chuyển nó dưới dạng tùy chọn:

class Client < ActiveRecord::Base
  acts_as_paranoid column: :destroyed_at

  ...
end

Sau khi hoàn tất việc cài đặt, khi gọi delete sẽ update giá trị cho trường deleted_at:

>> client.deleted_at
# => nil
>> client.destroy
# => client
>> client.deleted_at
# => [current timestamp]

Để thực hiện việc xóa hoàn toàn record, chúng ta chạy lệnh really_destroy!:

>> client.deleted_at
# => nil
>> client.really_destroy!
# => client

Để truy vấn tất cả các record đã bị xóa bởi soft delete:

Client.only_deleted

Để truy vấn tất cả các record đang tồn tại trong database, bao gồm đã xóa và chưa xóa:

Client.with_deleted

Nếu bạn muốn loại trừ các bản ghi đã xóa:

Client.without_deleted

Để kiểm tra xem record đó đã được soft delete hay chưa:

client.paranoia_destroyed?
# or
client.deleted?

Để khôi phục record đã bị soft delete:

Client.restore(id)
# or
client.restore
# or
Client.restore([id1, id2, ..., idN])

Nếu muốn khôi phục cả những record có quan hệ dependently destroyed:

Client.restore(id, :recursive => true)
# or
client.restore(:recursive => true)

Nếu muốn khôi phục cả những record có quan hệ dependently destroyed mà được xóa trong vòng 2 phút trước:

Client.restore(id, :recursive => true. :recovery_window => 2.minutes)
# or
client.restore(:recursive => true, :recovery_window => 2.minutes)

Với những bản ghi có quan hệ dependently destroyed cần lưu ý rằng bất kỳ phương thức nào được gọi trên model cha cũng sẽ được gọi trên model con. Ví dụ:

class Client < ActiveRecord::Base
  acts_as_paranoid

  has_many :emails, dependent: :destroy
end

class Email < ActiveRecord::Base
  acts_as_paranoid

  belongs_to :client
end

Khi ta gọi destroy ở model client thì phương thức destroy cũng được gọi ở model emails Ví dụ:

>> client.emails.count
# => 5
>> client.destroy
# => client
>> client.deleted_at
# => [current timestamp]
>> Email.where(client_id: client.id).count
# => 0
>> Email.with_deleted.where(client_id: client.id).count
# => 5

Tương tự như vậy, khi chúng ta gọi really_destroy! trên client, sau đó mỗi email của client cũng sẽ có really_destroy!:

>> client.emails.count
# => 5
>> client.id
# => 12345
>> client.really_destroy!
# => client
>> Client.find 12345
# => ActiveRecord::RecordNotFound
>> Email.with_deleted.where(client_id: client.id).count
# => 0

Tuy nhiên, nếu model Email không có act_as_paranoid, thì việc gọi destroy trên client cũng sẽ gọi hủy destroy các email của client đó và thực sự destroy chúng:

class Client < ActiveRecord::Base
  acts_as_paranoid

  has_many :emails, dependent: :destroy
end

class Email < ActiveRecord::Base
  belongs_to :client
end

>> client.emails.count
# => 5
>> client.destroy
# => client
>> Email.where(client_id: client.id).count
# => 0
>> Email.with_deleted.where(client_id: client.id).count
# => NoMethodError: undefined method `with_deleted' for #<Class:0x0123456>

Qua bài viết trên là mong các bạn sẽ có thêm kiến thức cơ bản cũng như cách sử dụng của Paranoia để áp dụng vào project Ruby on Rails của các bạn. Nếu thấy bài viết có ích thì up vote cho mình nhé ^^ Nguồn tài liệu: https://github.com/rubysherpas/paranoia