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 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 UserHouse để 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", "---\nid: 1\nname: Name_0\ncreated_at: 2015-12-30 10:20:52.589033000 Z\nupdated_at: 2015-12-30 10:20:52.589033000 Z\n"], ["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_id26 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: "---\nid: 1\nname: Name_0\ncreated_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_id26: PaperTrail::Version id: 26PaperTrail::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: "---\nid: 1\nname: Name_0\ncreated_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: "---\nid: 1\nname: Name_0_new\ncreated_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: "---\nid: 1\nname: Name_0_update_first\ncreated_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_id2829. 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", "---\nid: 1\nname: Name_0_update_second\ncreated_at: 2015-12-30 10:20:52.589033000 Z\nupdated_at: 2016-02-24 02:27:41.069064000 Z\n"], ["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", "---\nid: 1\nname: Name_0_3th\ncreated_at: 2015-12-30 10:20:52.589033000 Z\nupdated_at: 2016-02-24 02:32:45.390444000 Z\n"], ["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_id30.

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: "---\nid: 1\nname: Name_0_update_second\ncreated_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: "---\nid: 1\nname: Name_0_3th\ncreated_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ó idtransaction_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", "---\nid: 1\nname: name\ncreated_at: 2015-12-30 10:20:52.589033000 Z\nupdated_at: 2016-02-24 10:36:40.345223000 Z\n"], ["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.


All Rights Reserved