Tổng hợp gem thông dụng trong Rails - Phần 1: Kaminari và Paranoia

Giới thiệu

Mục đích là để làm một seri blogs tập hợp tất cả những gem gần gũi, thường gặp nhất trong các dự án Rails. Mỗi bài từ 2-3 gem, với mong muốn giúp ích một phần nào đó cho những bạn muốn học và mới bắt đầu học rails trong việc tìm kiếm những gem thông dụng

Đây sẽ là bài mở đầu trong chuỗi seri gem của mình. Mình xin giới thiệu 2 gem rất quen thuộc là:

1/Kaminari - phân trang/pagination

2/Paranoia - Xóa mềm/Soft delete

1. Kaminari

Tổng quan

  • Là một gem support cho việc phân trang rất phổ biến trong Rails, được hỗ trợ từ phiên bản Rails 4 trở lên

Cài đặt

Thêm vào Gemfile

gem 'kaminari'

rồi sau đó đọc thần chú bundle install để tiến hành cài gem

Sử dụng

Để phân trang cho một list object trong hàm index, ta cần viết như sau

  def index
    @object = ModelName.all.page params[:page]
  end

Ở đây, default sẽ phân ra 25 objects/1 trang. Để phân theo ý mình thì ta đổi lại @object = ModelName.all.page(params[:page]).per(10) . Như thế này sẽ lấy ra 10 objects/1 trang

Hoặc, bạn cũng có thể config số objects/1trang cho từng mỗi model bằng cách thêm dòng dưới đây vào file Model

class User < ActiveRecord::Base
  paginates_per 50
end

Để config lại default của gem, ta chạy lệnh rails g kaminari:config. Gem sẽ sinh ra 1 file config/initializers/kaminari_config.rb

Kaminari.configure do |config|
  # config.default_per_page = 25
  # config.max_per_page = nil
  # config.window = 4
  # config.outer_window = 0
  # config.left = 0
  # config.right = 0
  # config.page_method_name = :page
  # config.param_name = :page
end

Đó là tất cả những thứ cơ bản mà hầu như khi dùng đến Kaminari thì đều phải biết. Ngoài ra, phần nâng cao mời các bạn tham khảo thêm tại: https://github.com/kaminari/kaminari

2. Paranoia

Tổng quan

  • Thông thường, khi bạn xóa một record ở DB thì record đó sẽ biến mất hoàn toàn và không có cách nào để khôi phục (hard delete). Như vậy thật nguy hiểm, nếu chẳng may bạn lỡ tay hay vì bất cứ 1 lý do nào đó mà xóa bay mất 1 record trong DB cũng đủ cho bạn ra đảo.
  • Để khắc phục điểm này, gem Paranoia đã biến việc xóa cứng thành xóa mềm (soft delete), hay nói rõ ràng hơn là thêm vào table một column deleted_at với giá trị default là nil. Khi một record bị xóa thì sẽ update thời gian xóa vào column này thay vì cho record đó bay màu. Như vậy bạn có thể dễ dàng sửa chữa sai lầm mỗi khi có tai nạn xảy ra phải không nào :3

Cài đặt

Thêm vào Gemfile

 gem "paranoia"

rồi lại đọc thần chú bundle install

Tiếp theo ta chạy dòng lệnh sau với các model cần áp dụng xóa mềm bin/rails generate migration AddDeletedAtToTableName deleted_at:datetime:index

Và bạn sẽ có một file migration như sau:

class AddDeletedAtToTableName < ActiveRecord::Migration
  def change
    add_column :model_names, :deleted_at, :datetime
    add_index :model_names, :deleted_at
  end
end

Sử dụng

Ở Model file, ta thêm acts_as_paranoid :

class Model < ActiveRecord::Base
  acts_as_paranoid

  # ...
end

Cài đặt như vậy là xong rồi, bây giờ khi chúng ta gọi phương thức destroy, mọi bản ghi trong bảng Model đều sẽ được soft delete. Hãy thử xóa một record bất kỳ bằng phương thức destroy, bạn sẽ thấy câu query sẽ là Update thay vì Delete như trước

Model.first.destroy
----------------
# UPDATE "models" SET deleted_at = '2021-01-01 19:37:41.021123' WHERE "models"."deleted_at" IS NULL AND "models"."id" = 1

Hoặc nếu bạn muốn xóa cứng ns luông thì Model.first.really_destroy!

-Để lấy ra tất cả cả bản ghi chưa xóa ta dùng lệnh Model.without_deleted hoặc đơn giản là Model.all 😄

-Để lấy ra tất cả các bản ghi bao gồm bản ghi đã bị xóa mềm: *Model.with_deleted *

-Để "sửa chữa sai lầm" khi bạn lỡ tay xóa 1 bản ghi: Model.restore(id)

Giải thích

Bản chất của Paranoia là thêm một class method cho class ActiveRecord::Base là self.acts_as_paranoid

  def self.acts_as_paranoid(options={})
    ...
    def self.paranoia_scope
      where(paranoia_column => paranoia_sentinel_value)
    end
    ...

    unless options[:without_default_scope]
      default_scope { paranoia_scope }
    end
  ...
  end
end 

Do vậy trong model mỗi khi ta khai báo acts_as_paranoid thì class method này sẽ chạy

Việc thêm default_scope để loại bỏ những record theo setting của Paranoia. Giả sử đã add acts_as_paranoid vào model Order, khi gọi 1 câu scope bất kỳ từ bảng Model ta đều thấy:

Model.all
  Rails c> Model Load (1.9ms)  SELECT `t_model`.* FROM `t_model` WHERE `t_model`.`deleted_at` IS NULL

Nhược điểm của paranoia

  • Gem paranoia sử dụng default scope để loại bỏ các bản ghi bị đáng dấu là đã bị xóa ra khỏi các câu query "thông thường". Việc này có thể có tác dụng phụ không mong muốn như:

  • Sự hỗn độn giữa ORM và Raw SQL. Khi dự án bạn làm ngày một phình lớn lên, việc viết các câu SQL thuần để tăng hiệu năng là điều khó tránh khỏi. Và bất kỳ câu truy vấn SQL thuần nào cũng cần phải điểu chỉnh một cách thủ công để loại bỏ các bản ghi đã bị xóa mềm.

-Một vấn đề quan trọng cần xem xét là hiệu suất và khả năng mở rộng ảnh hưởng đến các bản ghi bị xóa mềm trên hệ thống. Mặc dù hầu hết các bản ghi như vậy có thể ẩn với hầu hết người dùng, chúng vẫn nằm trong cùng cơ sở dữ liệu với các bản ghi khác, và (tối thiểu là) sẽ tiếp tục chiếm không gian. Nếu các bản ghi bị xóa được đánh index (sẽ là mặc định, trừ khi bạn đánh index 1 phần và loại trừ chúng), thì bất kỳ index nào như vậy sẽ ảnh hưởng đến độ trễ khi thực hiện đọc/ghi dữ liệu.


All Rights Reserved