如何将虚拟属性status_name的变更存入PaperTrail的object_changes?
我尝试用PaperTrail的meta选项把虚拟属性status_name的变更持久化到object_changes中,已经按照文档要求在versions表添加了status_name字段,该属性能被持久化,但它被存在单独列里,而且没有包含变更前的值。
当前versions记录示例:
object_changes: "{\"updated_at\":[\"2023-01-18T22:50:22.394Z\",\"2023-01-18T22:50:22.408Z\"],\"status_id\":[1,2]}", status_name: "Status 2"
这符合gem默认行为,但我希望把status_name的变更(含新旧值)存入object_changes哈希里。目前通过meta绑定自定义方法实现了追踪,但方案太繁琐,想找替代方案,同时也在探索自定义object_changes适配器的可能性。
现有自定义方法实现:
has_paper_trail( meta: { status_name: :track_status_name_changes } ) def track_status_name_changes if versions.blank? [nil, status&.name].to_json # "[null,\"Status 1\"]" else prev_name = JSON.parse(versions.last.status_name).last [prev_name, status&.name].to_json # "[\"Status 1\",\"Status 2\"]" end end
方案1:自定义ObjectChanges适配器(推荐)
直接利用PaperTrail提供的适配器扩展,把虚拟属性的变更注入到diff结果中,无需维护额外的status_name列。
# config/initializers/paper_trail.rb PaperTrail.config.object_changes_adapter = CustomObjectChangesAdapter.new class CustomObjectChangesAdapter def diff(changes) # PaperTrail调用diff时会把上下文绑定到当前模型实例 record = self # 检查status_id是否发生变更,对应生成status_name的新旧值 status_id_key = changes.key?("status_id") ? "status_id" : :status_id if changes.key?(status_id_key) old_status_id, new_status_id = changes[status_id_key] old_status_name = old_status_id ? Status.find_by(id: old_status_id)&.name : nil new_status_name = new_status_id ? Status.find_by(id: new_status_id)&.name : nil # 将status_name的变更加入到object_changes的哈希中 changes["status_name"] = [old_status_name, new_status_name] end changes end end
这个适配器会在生成object_changes的diff时,自动检测status_id的变更,然后把对应的status_name新旧值添加到变更记录里,最终存入object_changes列。
方案2:手动追加变更到object_changes
在模型中监听status_id的变更事件,手动修改最新生成的version记录,把status_name的变更加入object_changes。
class YourModel < ApplicationRecord has_paper_trail after_save :add_status_name_to_object_changes, if: :saved_change_to_status_id? private def add_status_name_to_object_changes latest_version = versions.last return unless latest_version # 解析原object_changes并追加status_name的变更 object_changes = JSON.parse(latest_version.object_changes) object_changes["status_name"] = [ Status.find_by(id: saved_change_to_status_id[0])&.name, Status.find_by(id: saved_change_to_status_id[1])&.name ] latest_version.update!(object_changes: object_changes.to_json) end end
该方案适合不想修改全局适配器的场景,直接在模型内处理变更追加,注意并发更新场景可能需要加锁。
方案3:优化现有meta方法
如果仍想保留meta方式,可利用PaperTrail内置的previous_version方法简化逻辑,不用手动解析JSON。
class YourModel < ApplicationRecord has_paper_trail( meta: { status_name: :track_status_name_changes } ) def track_status_name_changes old_name = previous_version&.status&.name [old_name, status&.name].to_json end end
previous_version会直接返回上一个版本的模型实例,能直接获取旧版本的status.name,大幅简化代码,但仍会把status_name存在单独列中。
内容的提问来源于stack exchange,提问作者abax

