你必須很努力

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

2021/09/18
字數統計: 1.1k閱讀時間: 6 min

前言

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

如何安裝

在 Gemfile 中加入該 paranoia Gem

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

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

如何使用

rails console --sandbox 中演練示範

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
$ 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 時,軟刪除的資料並沒有真的被刪除,此時會有問題

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
$ 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/

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

原文連結:https://riverye.com/2021/09/18/Day05-Gem-paranoia-軟刪除介紹與應用/

發表日期:2021-09-18

更新日期:2022-12-21

CATALOG
  1. 1. 前言
  2. 2. 如何安裝
  3. 3. 如何使用
  4. 4. 注意
  5. 5. 小結
  6. 6. 參考資料