12/08/2018, 13:14

Transaction_id trong PaperTrail

Trong việc sử dụng gem PaperTrail (https://github.com/airblade/paper_trail) để tạo log, việc quản lý tranction_id đôi lúc gặp khá nhiều vấn đề, bài viết sau hi vọng giúp bạn phần nào. Đầu tiên transaction_id có tác dụng đánh dấu những version được tạo ra cùng 1 thời điểm hoặc trong cùng 1 action ...

Trong việc sử dụng gem PaperTrail (https://github.com/airblade/paper_trail) để tạo log, việc quản lý tranction_id đôi lúc gặp khá nhiều vấn đề, bài viết sau hi vọng giúp bạn phần nào.

Đầu tiên transaction_id có tác dụng đánh dấu những version được tạo ra cùng 1 thời điểm hoặc trong cùng 1 action submit.

Tại bài viết trước (https://viblo.asia/pham.huy.cuong/posts/E7bGo9LOR5e2) đã trình bày về việc log cho các mối quan hệ nhiều nhiều bằng gem này, ta vẫn sử dụng các model User và House để làm ví dụ.

Tại console update bản ghi đầu tiên của User:

User.first.update house_ids: [3,4], name: "Name_0_new"

Action tạo version của đối với bản ghi đầu tiên của model House là :

SQL (0.1ms)  INSERT INTO "versions" ("event", "object", "created_at", "transaction_id", "item_id", "item_type") VALUES (?, ?, ?, ?, ?, ?)  [["event", "update"], ["object", "---
id: 1
name: Name_0
created_at: 2015-12-30 10:20:52.589033000 Z
updated_at: 2015-12-30 10:20:52.589033000 Z
"], ["created_at", "2016-02-24 01:50:43.120334"], ["transaction_id", 26], ["item_id", 1], ["item_type", "User"]]

Ta có thể thấy transaction_id được gán vào là 26, thử gọi ra tất cả các version được gán transaction_id là 26 ta có:

PaperTrail::Version.where transaction_id: 26
PaperTrail::Version Load (0.4ms)  SELECT "versions".* FROM "versions" WHERE "versions"."transaction_id" = ?  [["transaction_id", 26]]
=> #<ActiveRecord::Relation [#<PaperTrail::Version id: 26, item_type: "HousesUser", item_id: 11, event: "create", whodunnit: nil, object: nil, created_at: "2016-02-24 01:50:43", transaction_id: 26>, #<PaperTrail::Version id: 27, item_type: "User", item_id: 1, event: "update", whodunnit: nil, object: "---
id: 1
name: Name_0
created_at: 2015-12-30 10:2...", created_at: "2016-02-24 01:50:43", transaction_id: 26>]>

Ta thấy có 2 version được tạo cùng với 1 transaction_id là 26: PaperTrail::Version id: 26 và PaperTrail::Version id: 27.

PaperTrail::Version id: 26 được tạo cho bản ghi của model HousesUser:

PaperTrail::Version.find 26
PaperTrail::Version Load (0.1ms)  SELECT  "versions".* FROM "versions" WHERE "versions"."id" = ? LIMIT 1  [["id", 26]]
=> #<PaperTrail::Version id: 26, item_type: "HousesUser", item_id: 11, event: "create", whodunnit: nil, object: nil, created_at: "2016-02-24 01:50:43", transaction_id: 26>
irb(main):012:0>

PaperTrail::Version id: 27 được tạo cho bản ghi của model User:

PaperTrail::Version.find 27
PaperTrail::Version Load (0.2ms)  SELECT  "versions".* FROM "versions" WHERE "versions"."id" = ? LIMIT 1  [["id", 27]]
=> #<PaperTrail::Version id: 27, item_type: "User", item_id: 1, event: "update", whodunnit: nil, object: "---
id: 1
name: Name_0
created_at: 2015-12-30 10:2...", created_at: "2016-02-24 01:50:43", transaction_id: 26>

2 version được tạo ra có cùng 1 transaction_id vì cùng đươc submit 1 lúc. Từ đây ta có thể tìm được các version được tạo ra cùng lúc với 1 version bằng scope trong model version:

scope :submit_with, ->do
  PaperTrail::Version.where transaction_id: transaction_id
end

Khi update bản ghi đầu tiên của model User, 2 version với 2 transaction_id được tạo ra:

User.first.update name: "Name_0_update_first"
User.first.update name: "Name_0_update_second"
User.first.versions.last 2
User Load (0.4ms)  SELECT  "users".* FROM "users"  ORDER BY "users"."id" ASC LIMIT 1
  PaperTrail::Version Load (0.5ms)  SELECT "versions".* FROM "versions" WHERE "versions"."item_id" = ? AND "versions"."item_type" = ?  ORDER BY "versions"."created_at" ASC, "versions"."id" ASC  [["item_id", 1], ["item_type", "User"]]
=> [#<PaperTrail::Version id: 28, item_type: "User", item_id: 1, event: "update", whodunnit: nil, object: "---
id: 1
name: Name_0_new
created_at: 2015-12-30 ...", created_at: "2016-02-24 02:27:34", transaction_id: 28>, #<PaperTrail::Version id: 29, item_type: "User", item_id: 1, event: "update", whodunnit: nil, object: "---
id: 1
name: Name_0_update_first
created_at: 20...", created_at: "2016-02-24 02:27:41", transaction_id: 29>]

Ta thấy 2 version được tạo ra với transaction_id là 28 và 29. Vậy nếu muốn tạo ra các version được submit tại 2 thời điểm khác nhau có cùng 1 transaction_id ta phải làm như thế nào? Có thể gói trong 1 transaction do end.

User.transaction do
  User.first.update name: "Name_0_3th"
  User.first.update name: "Name_0_4th"
end
(0.2ms)  begin transaction
  User Load (0.3ms)  SELECT  "users".* FROM "users"  ORDER BY "users"."id" ASC LIMIT 1
  SQL (0.3ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "Name_0_3th"], ["updated_at", "2016-02-24 02:32:45.390444"], ["id", 1]]
  SQL (0.1ms)  INSERT INTO "versions" ("event", "object", "created_at", "item_id", "item_type") VALUES (?, ?, ?, ?, ?)  [["event", "update"], ["object", "---
id: 1
name: Name_0_update_second
created_at: 2015-12-30 10:20:52.589033000 Z
updated_at: 2016-02-24 02:27:41.069064000 Z
"], ["created_at", "2016-02-24 02:32:45.390444"], ["item_id", 1], ["item_type", "User"]]
  SQL (0.1ms)  UPDATE "versions" SET "transaction_id" = ? WHERE "versions"."id" = ?  [["transaction_id", 30], ["id", 30]]
  User Load (0.1ms)  SELECT  "users".* FROM "users"  ORDER BY "users"."id" ASC LIMIT 1
  SQL (0.0ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "Name_0_4th"], ["updated_at", "2016-02-24 02:32:45.395115"], ["id", 1]]
  SQL (0.0ms)  INSERT INTO "versions" ("event", "object", "created_at", "transaction_id", "item_id", "item_type") VALUES (?, ?, ?, ?, ?, ?)  [["event", "update"], ["object", "---
id: 1
name: Name_0_3th
created_at: 2015-12-30 10:20:52.589033000 Z
updated_at: 2016-02-24 02:32:45.390444000 Z
"], ["created_at", "2016-02-24 02:32:45.395115"], ["transaction_id", 30], ["item_id", 1], ["item_type", "User"]]
  (147.4ms)  commit transaction
=> true

Ta thấy 2 version được tạo ra với cùng 1 transaction_id là 30.

Transaction_id được tính như thế nào?, thực chất việc ghi lại transaction_id nhằm phân biệt các version tại các lần submit khác nhau, việc tính toán transaction_id khá đơn gỉan, nó được lấy bằng id của version đầu tiên được tạo ra tại 1 lần submit (hay trong 1 transaction do end).

User.first.versions.last 2
  User Load (0.3ms)  SELECT  "users".* FROM "users"  ORDER BY "users"."id" ASC LIMIT 1
  PaperTrail::Version Load (0.3ms)  SELECT "versions".* FROM "versions" WHERE "versions"."item_id" = ? AND "versions"."item_type" = ?  ORDER BY "versions"."created_at" ASC, "versions"."id" ASC  [["item_id", 1], ["item_type", "User"]]
=> [#<PaperTrail::Version id: 30, item_type: "User", item_id: 1, event: "update", whodunnit: nil, object: "---
id: 1
name: Name_0_update_second
created_at: 2...", created_at: "2016-02-24 02:32:45", transaction_id: 30>, #<PaperTrail::Version id: 31, item_type: "User", item_id: 1, event: "update", whodunnit: nil, object: "---
id: 1
name: Name_0_3th
created_at: 2015-12-30 ...", created_at: "2016-02-24 02:32:45", transaction_id: 30>]

Ta dễ dàng nhận thấy version đầu tiên được tạo ra có id và transaction_id đều là 30.

Có thể xem ở https://github.com/airblade/paper_trail/blob/master/lib/paper_trail/has_paper_trail.rb#L458

def set_transaction_id(version)
  return unless self.class.paper_trail_version_class.column_names.include?('transaction_id')
  if PaperTrail.transaction? && PaperTrail.transaction_id.nil?
    PaperTrail.transaction_id = version.id
    version.transaction_id = version.id
    version.save
  end
end

Việc gán theo id của version đầu tiên trong lượt submit được tạo ra luôn chắc chắn rằng transaction_id ở 2 lượt submit khác nhau là không trùng nhau.

Muốn set transaction_id cho version sắp được tạo ra, ta chỉ việc gán PaperTrail.transaction_id với gía trị mong muốn trước khi submit.

PaperTrail.transaction_id = 100
=> 100
irb(main):010:0> User.first.update name: "name1"
  User Load (0.3ms)  SELECT  "users".* FROM "users"  ORDER BY "users"."id" ASC LIMIT 1
   (0.1ms)  begin transaction
  SQL (0.3ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "name1"], ["updated_at", "2016-02-24 10:38:27.471171"], ["id", 1]]
  SQL (0.2ms)  INSERT INTO "versions" ("event", "object", "created_at", "transaction_id", "item_id", "item_type") VALUES (?, ?, ?, ?, ?, ?)  [["event", "update"], ["object", "---
id: 1
name: name
created_at: 2015-12-30 10:20:52.589033000 Z
updated_at: 2016-02-24 10:36:40.345223000 Z
"], ["created_at", "2016-02-24 10:38:27.471171"], ["transaction_id", 100], ["item_id", 1], ["item_type", "User"]]
   (153.2ms)  commit transaction

Ta thấy transaction_id của version mới được tạo ra là 100 đúng với số được gán cho PaperTrail.transaction_id trước đó. Tuy nhiên việc gán này làm cho ý nghĩa của transaction_id trong việc đánh dấu những version thuộc 1 lần submit không còn nữa.

Cảm ơn và hi vọng bài viết giúp ích phần nào trong công việc của bạn.

0