Dùng PaperTrail log cho quan hệ has_and_belongs_to_many

Trong việc sử dụng Gem PaperTrail (https://github.com/airblade/paper_trail) để log cho các mối quan hê has_and_belongs_to_many đôi khi gặp nhiều vấn đề, bài viết sau hi vọng giúp ích phần nào trong công việc của bạn. https://github.com/airblade/paper_trail#associations phần này để tạo association cho phần log.

Gỉa sử có 2 model User và House

class User < ActiveRecord::Base
  has_paper_trail

  has_and_belongs_to_many :houses
end

class House < ActiveRecord::Base
  has_paper_trail

  has_and_belongs_to_many :users
end

create_table "houses", force: :cascade do |t|
  t.string   "name"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

create_table "users", force: :cascade do |t|
  t.string   "name"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

Paper trail không tạo version đối với quan hệ has_and_belongs_to_many, do vậy phải chuyển thành dạng quan hệ has_many và thêm has_paper_trail vào model quan hệ (house_user) trước khi muốn tạo log

class User < ActiveRecord::Base
  has_paper_trail

  has_many :houses_users
  has_many :houses, through: :houses_users
end

class House < ActiveRecord::Base
  has_paper_trail

  has_many :houses_users
  has_many :users, through: :houses_users
end

class HousesUser < ActiveRecord::Base
  has_paper_trail

  belongs_to :user
  belongs_to :house
end

Bây giờ, tại console ta thử gán cho phần tử đầu tiên của User quan hệ với 1 phần tử của House

irb(main):011:0> User.first.house_ids = [3]

Ta sẽ thấy version của model HousesUser được tạo ra

SQL (0.1ms)  INSERT INTO "versions" ("event", "created_at", "item_id", "item_type") VALUES (?, ?, ?, ?)  [["event", "create"], ["created_at", "2016-01-08 10:34:03.595991"], ["item_id", 10], ["item_type", "HousesUser"]]

Và version_association được cũng đồng thời được tạo ra

SQL (0.1ms)  INSERT INTO "version_associations" ("version_id", "foreign_key_name", "foreign_key_id")
VALUES (?, ?, ?)  [["version_id", 25], ["foreign_key_name", "user_id"], ["foreign_key_id", 1]]
  SQL (0.0ms)  INSERT INTO "version_associations" ("version_id", "foreign_key_name", "foreign_key_id") VALUES (?, ?, ?)  [["version_id", 25], ["foreign_key_name", "house_id"], ["foreign_key_id", 3]]
   (137.7ms)  commit transaction
=> [3]

Version của model HouseUser được tạo ra với event: "create" nếu như quan hệ mới được thêm vào và event: "destroy" nếu như quan hệ mới được bỏ đi, còn tại model PaperTrail::VersionAssociation, bản ghi mới được tạo ra, ở bản ghi này ta thấy:

<PaperTrail::VersionAssociation id: 13, version_id: 25, foreign_key_name: "user_id", foreign_key_id: 1>
<PaperTrail::VersionAssociation id: 14, version_id: 25, foreign_key_name: "house_id", foreign_key_id: 3>

version_id: là id của version được tạo cho model HouseUser, foreign_key_name: được lấy từ foreign_key taị model HouseUser, vì ở đây có 2 foreign_key chính là refrence tới 2 model House và User nên 2 bản ghi của PaperTrail::VersionAssociation được tạo ra để ghi lại những thay đổi này, foreign_key_id: là gía trị của attibute reference trên tại bản ghi của model HouseUser.

Từ đó ta có 2 cách viết scope để lấy ra các version của HouseUser tạo ra mỗi khi thêm các quan hệ giữa 1 User với 1 House:

PaperTrail::Version.where(item_type: "HousesUser").where_object user_id: 1

Cách này sử dụng 1 trong 2 scope do gem cung cấp sẵn là where_object, where_object_changes, tuy nhiên 2 scope này sử dụng cách so sánh string trong dữ liệu nên nhiều lúc đưa ra kết qủa chưa chính xác, tỉ lệ không nhiều lắm.

"SELECT `versions`.* FROM `versions` WHERE `versions`.`item_type` = 'HousesUser' AND (`versions`.`object` LIKE '%\\nuser_id: 1\\n%')"

Đoạn ("versions"."object" LIKE "%\\nuser_id: 1\\n%") Là nguyên nhân có thể dẫn đến việc query ra những kết qủa sai, vì trong trường hợp nào đó, user có 1 trường là string và có gía trị như trong phần LIKE thì lúc query bản ghi cũng được lấy vào.

Cách dùng bảng PaperTrail::VersionAssociation để tìm được viết như sau:

PaperTrail::Version.where(item_type: "HousesUser").joins(:version_associations).where(version_associations: {foreign_key_name: "user_id", foreign_key_id: 1})

Từ câu query trên, ta có thể tìm được những thay đổi trong association và log lại được, cảm ơn bạn và hi vọng bài viết sẽ giúp ích phần nào trong công việc của bạn.


All Rights Reserved