iT邦幫忙

2021 iThome 鐵人賽

DAY 5
0
自我挑戰組

Ruby on Rails 與它們相關的東西 II系列 第 5

Day05 - Gem-paranoia 軟刪除介紹與應用

前言

對 ActiveReord 進行軟刪除 (Soft Deletion) 時,可透過自行實作 (ex: table 增加一欄,判斷是否被軟刪除),或直接用現成的 Gem 來處理

如何安裝

在 Gemfile 中加入該 paranoia Gem

需要增加軟刪除的 table 要加 deleted_at:datetime,並在該 model 中加入 acts_as_paranoid 即,可參考此 commit

推薦至 GitHub 看文件,寫得很清楚,且有提供範例

如何使用

rails console --sandbox 中演練示範

$ rails c -s

[1] pry(main)> Shop.count
  TRANSACTION (1.1ms)  BEGIN
   (16.6ms)  SELECT COUNT(*) FROM "shops" WHERE "shops"."deleted_at" IS NULL
0
[2] pry(main)> Shop.create(name: 'riverye', email: 'river@riverye.com');
  TRANSACTION (0.3ms)  SAVEPOINT active_record_1
  Shop Exists? (0.7ms)  SELECT 1 AS one FROM "shops" WHERE "shops"."name" = $1 AND "shops"."deleted_at" IS NULL LIMIT $2  [["name", "riverye"], ["LIMIT", 1]]
  Shop Create (1.8ms)  INSERT INTO "shops" ("name", "email", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["name", "riverye"], ["email", "river@riverye.com"], ["created_at", "2021-07-18 06:07:26.190110"], ["updated_at", "2021-07-18 06:07:26.190110"]]
  TRANSACTION (0.2ms)  RELEASE SAVEPOINT active_record_1
[3] pry(main)> Shop.count
   (0.6ms)  SELECT COUNT(*) FROM "shops" WHERE "shops"."deleted_at" IS NULL
1
[4] pry(main)> Shop.first.destroy
  Shop Load (0.5ms)  SELECT "shops".* FROM "shops" WHERE "shops"."deleted_at" IS NULL ORDER BY "shops"."id" ASC LIMIT $1  [["LIMIT", 1]]
  TRANSACTION (0.3ms)  SAVEPOINT active_record_1
  Shop Update (0.7ms)  UPDATE "shops" SET "deleted_at" = $1, "updated_at" = $2 WHERE "shops"."id" = $3  [["deleted_at", "2021-07-18 06:07:43.934539"], ["updated_at", "2021-07-18 06:07:43.934564"], ["id", 1]]
  TRANSACTION (0.2ms)  RELEASE SAVEPOINT active_record_1
#<Shop:0x00007fa2ec4d0468> {
            :id => 1,
          :name => "riverye",
         :email => "river@riverye.com",
          :note => nil,
    :created_at => Sun, 18 Jul 2021 14:07:26.190110000 CST +08:00,
    :updated_at => Sun, 18 Jul 2021 14:07:43.934564000 CST +08:00,
    :deleted_at => Sun, 18 Jul 2021 14:07:43.934539400 CST +08:00
}
[5] pry(main)> Shop.count
   (0.6ms)  SELECT COUNT(*) FROM "shops" WHERE "shops"."deleted_at" IS NULL
0
[6] pry(main)> Shop.with_deleted.count
   (0.4ms)  SELECT COUNT(*) FROM "shops"
1

# 上述步驟說明:
# 1. 一開始 Shop.count        # 0
# 2. 建立一個 Shop
# 3. Shop.count              # 1
# 4. 刪除建立的 Shop
# 5. Shop.count              # 0
# 6. Shop.with_deleted.count # 1

注意

當 table 有 unique key 時,軟刪除的資料並沒有真的被刪除,此時會有問題

$ rails c -s

[1] pry(main)> Shop.create!(name: 'riverye', email: 'river@riverye.com');
  TRANSACTION (0.3ms)  BEGIN
  TRANSACTION (0.5ms)  SAVEPOINT active_record_1
  Shop Exists? (16.5ms)  SELECT 1 AS one FROM "shops" WHERE "shops"."name" = $1 AND "shops"."deleted_at" IS NULL LIMIT $2  [["name", "riverye"], ["LIMIT", 1]]
  Shop Create (1.6ms)  INSERT INTO "shops" ("name", "email", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["name", "riverye"], ["email", "river@riverye.com"], ["created_at", "2021-07-18 06:35:26.540395"], ["updated_at", "2021-07-18 06:35:26.540395"]]
  TRANSACTION (0.3ms)  RELEASE SAVEPOINT active_record_1
[2] pry(main)> Shop.first.destroy;
  Shop Load (0.8ms)  SELECT "shops".* FROM "shops" WHERE "shops"."deleted_at" IS NULL ORDER BY "shops"."id" ASC LIMIT $1  [["LIMIT", 1]]
  TRANSACTION (0.3ms)  SAVEPOINT active_record_1
  Shop Update (0.9ms)  UPDATE "shops" SET "deleted_at" = $1, "updated_at" = $2 WHERE "shops"."id" = $3  [["deleted_at", "2021-07-18 06:35:40.312456"], ["updated_at", "2021-07-18 06:35:40.312482"], ["id", 1]]
  TRANSACTION (0.3ms)  RELEASE SAVEPOINT active_record_1
[3] pry(main)> Shop.create!(name: 'riverye', email: 'river@riverye.com');
  TRANSACTION (0.3ms)  SAVEPOINT active_record_1
  Shop Exists? (0.4ms)  SELECT 1 AS one FROM "shops" WHERE "shops"."name" = $1 AND "shops"."deleted_at" IS NULL LIMIT $2  [["name", "riverye"], ["LIMIT", 1]]
  Shop Create (1.9ms)  INSERT INTO "shops" ("name", "email", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["name", "riverye"], ["email", "river@riverye.com"], ["created_at", "2021-07-18 06:35:59.189116"], ["updated_at", "2021-07-18 06:35:59.189116"]]
  TRANSACTION (0.2ms)  ROLLBACK TO SAVEPOINT active_record_1
# ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint "index_shops_on_name"
# DETAIL:  Key (name)=(riverye) already exists.

# from /xxx/yyy/.rvm/gems/ruby-3.0.1/gems/rack-mini-profiler-2.3.2/lib/patches/db/pg.rb:69:in `exec_params'
# Caused by PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint "index_shops_on_name"
# DETAIL:  Key (name)=(riverye) already exists.

# from /xxx/yyy/.rvm/gems/ruby-3.0.1/gems/rack-mini-profiler-2.3.2/lib/patches/db/pg.rb:69:in `exec_params'
[4] pry(main)> Shop.with_deleted.first.really_destroy!;
  Shop Load (0.4ms)  SELECT "shops".* FROM "shops" ORDER BY "shops"."id" ASC LIMIT $1  [["LIMIT", 1]]
  TRANSACTION (0.2ms)  SAVEPOINT active_record_1
  Shop Update (0.4ms)  UPDATE "shops" SET "deleted_at" = $1, "updated_at" = $2 WHERE "shops"."id" = $3  [["deleted_at", "2021-07-18 06:36:08.980456"], ["updated_at", "2021-07-18 06:36:08.980466"], ["id", 1]]
  Shop Destroy (0.5ms)  DELETE FROM "shops" WHERE "shops"."id" = $1  [["id", 1]]
  TRANSACTION (0.4ms)  RELEASE SAVEPOINT active_record_1
[5] pry(main)> Shop.create!(name: 'riverye', email: 'river@riverye.com');
  TRANSACTION (0.4ms)  SAVEPOINT active_record_1
  Shop Exists? (0.5ms)  SELECT 1 AS one FROM "shops" WHERE "shops"."name" = $1 AND "shops"."deleted_at" IS NULL LIMIT $2  [["name", "riverye"], ["LIMIT", 1]]
  Shop Create (0.4ms)  INSERT INTO "shops" ("name", "email", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["name", "riverye"], ["email", "river@riverye.com"], ["created_at", "2021-07-18 06:36:48.398434"], ["updated_at", "2021-07-18 06:36:48.398434"]]
  TRANSACTION (0.3ms)  RELEASE SAVEPOINT active_record_1

# 上述步驟說明:
# 1. 建立一個 Shop
# 2. 軟刪除建立的 Shop
# 3. 建立一個 Shop (與第一個 name 一樣) # 建立失敗
# 4. 真的刪除軟刪除的 Shop
# 5. 建立一個 Shop (與第一個 name 一樣) # 建立成功

小結

實務上,常用的 Gem 之一,簡易好上手

參考資料

  1. paranoia GitHub
  2. Rails 實戰聖經

鐵人賽文章連結:https://ithelp.ithome.com.tw/articles/10264573
medium 文章連結:https://link.medium.com/ay1JSdj2Mjb
本文同步發布於 小菜的 Blog https://riverye.com/

備註:之後文章修改更新,以個人部落格為主


上一篇
Day04 - Gem-activerecord-import 批次建立介紹與應用
下一篇
Day06 - 監控 Sidekiq 有無正常運作(或執行超過多久)
系列文
Ruby on Rails 與它們相關的東西 II30

尚未有邦友留言

立即登入留言