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.