@changed_attributes trong việc log bằng PaperTrail

Trong việc log bằng PaperTrail, đôi khi việc đưa thêm các gía trị mong muốn vào phần lưu log gặp khó khăn, bài viết sau đây hi vọng gíup đỡ phần nào công việc của bạn.

Link gem PaperTrail https://github.com/airblade/paper_trail

Các model được sử dụng ở trong bài viết https://viblo.asia/pham.huy.cuong/posts/E7bGo9LOR5e2

Vậy ta có các bảng User, House, HousesUser

Thử update bản ghi đầu tiên của bảng User trường name và xem log của bản ghi này

irb(main):011:0> User.first.update name: "new_name"
irb(main):012:0> User.first.versions.last.object_changes
=> "---\nname:\n- name\n- new_name\nupdated_at:\n- 2016-04-08 09:17:04.741919000 Z\n- 2016-04-08 09:17:40.047259615 Z\n"

Chuyển thành hash ta gọi changeset

irb(main):015:0> User.first.versions.last.changeset
=> {"name"=>["name", "new_name"], "updated_at"=>[2016-04-08 09:17:04 UTC, 2016-04-08 09:17:40 UTC]}

Bây giờ gỉa sử ta muốn đưa thêm trường version_index lưu lại lượt thay đổi của record vào object_changes, ta thêm vào model User

before_save :create_version_index

private
def create_version_index
  @changed_attributes ||= {}
  @changed_attributes.merge! version_index: nil
end

def version_index
  versions.count
end

Update lại trường name ta thấy

irb(main):016:0> User.first.update name: "name"
irb(main):017:0> User.first.versions.last.changeset
=> {"name"=>["new_name", "name"], "version_index"=>[nil, 10], "updated_at"=>[2016-04-08 09:17:40 UTC, 2016-04-08 09:24:11 UTC]}

Thử gán lại trường name nhưng chưa save ta sẽ hiểu rõ hơn về @changed_attributes

irb(main):018:0> user = User.first
irb(main):019:0> user.name = "new_name"
irb(main):020:0> user.changed_attributes
=> {"name"=>"name"}
irb(main):025:0> user.changes
=> {"name"=>["name", "new_name"]}

Trong PaperTrail, hàm sinh ra object_changeschanges_for_paper_trail

https://github.com/airblade/paper_trail/blob/master/lib/paper_trail/has_paper_trail.rb#L395

def changes_for_paper_trail
  notable_changes = changes.delete_if { |k, _v| !notably_changed.include?(k) }
  self.class.serialize_attribute_changes_for_paper_trail!(notable_changes)
  notable_changes.to_hash
end

PaperTrail dùng hàm changes để lấy ra những attributes thay đổi và gía trị cũ và mới của nó, hàm changes gọi đến hàm changed_attributes để tìm những attributes có gía trị thay đổi.

https://github.com/rails/rails/blob/9334223c51839addb5bbf7c61f33644f250999a1/activemodel/lib/active_model/dirty.rb#L141

def changed
  changed_attributes.keys
end

https://github.com/rails/rails/blob/9334223c51839addb5bbf7c61f33644f250999a1/activemodel/lib/active_model/dirty.rb#L171

def changed_attributes
  @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
end

Ở đây ta thấy việc thay đổi @changed_attributes sẽ gíup ta đưa thêm các gía trị muốn thêm vào hash giữ các attributes và các gía trị thay đổi của nó.

Đối với các attribute thông thường thì việc đưa thêm value là như vậy, còn với các mối quan hệ has_many thì việc log sẽ như thế nào?, tại bài viết https://viblo.asia/pham.huy.cuong/posts/E7bGo9LOR5e2 đã trình bày cách log đối với dạng quan hệ này theo cách dùng cách tạo version cho bảng trung gian HousesUser, còn bằng cách sử dụng @changed_attributes thì có thể làm theo cách sau:

Tại model User ta thay đổi 1 chút trong cách khai báo has_many đối với houses

has_many :houses, through: :houses_users,
  before_add: :create_association_params,
  before_remove: :create_association_params

before_add, before_remove là hàm call_back để mỗi thi thêm hoặc bớt các mối quan hệ giữa 1 user với 1 house thì sẽ gọi đến hàm create_association_params, thêm vào private của model User:

def create_association_params record
  @changed_attributes ||= {}
  unless @changed_attributes[:house_ids]
    @changed_attributes.merge! house_ids: old_house_ids
  end
end

def old_house_ids
  houses.pluck :id
end

Thử update house_ids cho bản ghi đầu tiên của User ta thấy:

irb(main):007:0> u = User.first
irb(main):008:0> u.update house_ids: [1,2]
irb(main):009:0> u.versions.last.changeset
=> {"house_ids"=>[[5, 6], [1, 2]], "updated_at"=>[2016-04-13 01:39:04 UTC, 2016-04-13 01:39:28 UTC], "version_index"=>[nil, 48]}

Chú ý house_ids đã được log lại "house_ids"=>[[5, 6], [1, 2]].

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


All Rights Reserved