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: '[email protected]');
  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", "[email protected]"], ["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 => "[email protected]",
          :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: '[email protected]');
  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", "[email protected]"], ["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: '[email protected]');
  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", "[email protected]"], ["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: '[email protected]');
  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", "[email protected]"], ["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/

备注:之後文章修改更新,以个人部落格为主


<<:  【Day 03】- Python 基础操作与常见资料型态(整数、浮点数、布林值、字串、串列、元组、字典)

>>:  前两次题目的结论 | ML#Day10

Ruby on Rails View Helper

除了上⾯提到的局部样版,View Helper 也是⽤来整理程序码很常⽤的⼿法。其实平 常⼤家在写的...

Day 2 这些角落生物你可曾了解他

在service planner team规划设计产品服务时,亦负责拟定规划官网及app等四种隐私相...

Day44 ( 游戏设计 ) Flappy Bird

Flappy Bird 教学原文参考:Flappy Bird 这篇文章会介绍,如何在 Scratch...

来做一个跟屁虫镁光灯

标题听起来很厉害(?),不过今天只需要认识一个 Web API - Element.getBound...

[前端暴龙机,Vue2.x 进化 Vue3 ] Day22. Vue 旅游小帮手(完成)

终於来到 Vue2.x 介绍的尾声了,藉由完成今天自己的旅游小帮手范例一起收尾吧,GO~ 加入 Yo...