后端【免费下载链接】paper_trailTrack changes to your rails models项目地址https://gitcode.com/gh_mirrors/pa/paper_trail点击查看免费下载导读PaperTrail 的核心能力是把模型每次变更以Version记录的形式写入versions表其中object与object_changes两列承载了变更前后的属性快照。本文围绕 lib/paper_trail/attribute_serializers/README.md 定义的序列化serialization与反序列化deserialization概念深入剖析该模块的四个核心类及其在写入、读取两条链路中的调用关系帮助你理解属性值在模型内存态 → 数据库文本/JSON 列之间的转换机制并掌握 enum、时间类型、PostgreSQL 数组、Rails 内置加密属性等特殊场景的处理细节。一、概念定位什么是属性序列化按官方 README 的定义在本模块语境下Serialization序列化指为插入数据库而准备数据特指写入versions表的object与object_changes列之前对属性值进行的预处理Deserialization反序列化指从数据库读出数据后的任何处理例如准备VersionConcern#changeset的返回值lib/paper_trail/version_concern.rb。一个直观例子出自 cast_attribute_serializer.rb 的类注释字符串1.99在赋给ActiveRecord::Type::Integer类型的属性时会被序列化为整数1。也就是说这里的序列化不等于 JSON/YAML 文本化而是先按 Active Record 的类型系统把值转换为规范形式再由PaperTrail.serializer决定如何落地为文本。二、模块结构总览lib/paper_trail/attribute_serializers/目录下共有四个文件职责分工非常清晰文件类/模块职责object_attribute.rbObjectAttribute序列化 / 反序列化version.object列object_changes_attribute.rbObjectChangesAttribute序列化 / 反序列化version.object_changes列cast_attribute_serializer.rbCastAttributeSerializer对单个属性值按模型列类型做类型转换cast并对 enum、时间类型做特殊处理attribute_serializer_factory.rbAttributeSerializerFactory工厂把某些不适合 JSON 写入文本列的 Active Record 默认序列化器替换为 PaperTrail 自定义实现前两个类是列级入口操作一整份属性哈希后两个是值级实现处理单个键值对。此外工厂替换出来的自定义序列化器位于 lib/paper_trail/type_serializers/postgres_array_serializer.rb。三、写入链路变更如何被序列化进 versions 表3.1 事件层入口Version记录由事件Event类生成。在 events/base.rb 中可以看到两条入口object_attrs_for_paper_trail(is_touch)第 265-269 行先取变更前的非跳过属性再调用AttributeSerializers::ObjectAttribute.new(record.class).serialize(attrs)serialize_object_changes(changes)第 332-340 行调用AttributeSerializers::ObjectChangesAttribute.new(record.class).serialize(changes)随后由recordable_object_changes第 285-299 行决定若object_changes列是 PostgreSQL 的json类型则直接以哈希赋值否则用PaperTrail.serializer.dump(changes)落成文本。3.2 列级序列化器ObjectAttribute / ObjectChangesAttribute两个类结构对称核心都是alter(attributes, serialization_method)def alter(attributes, serialization_method) # Dont serialize non-encrypted before values before inserting into columns of type # JSON on PostgreSQL databases. attributes_to_serialize object_col_is_json? ? attributes.slice(*encrypted_attributes) : attributes return attributes if attributes_to_serialize.blank? serializer CastAttributeSerializer.new(model_class) attributes_to_serialize.each do |key, value| attributes[key] serializer.send(serialization_method, key, value) end attributes endobject_attribute.rbObjectChangesAttribute逻辑一致区别在于它对每个属性处理的是[before, after]二元数组见 object_changes_attribute.rb这段代码揭示了一个关键分支当object/object_changes列是 PostgreSQL 的json/jsonb类型时只序列化加密属性其余属性原样交给 AR 的 JSON 列类型去处理只有当列是text类型时才逐属性做类型转换。判断方法object_col_is_json?/object_changes_col_is_json?定义在 version_concern.rb即检查columns_hash[object].type是否属于%i[json jsonb]。3.3 值级转换CastAttributeSerializerCastAttributeSerializer是整个模块的类型转换中枢其serialize/deserialize逻辑cast_attribute_serializer.rbdef deserialize(attr, val) if defined_enums[attr] val.is_a?(::String) # Because PT 4 used to save the string version of enums to object_changes val elsif val.is_a?(ActiveRecord::Type::Time::Value) # Because Rails 7 time attribute throws a delegation error when you deserialize # it with the factory. val.instance_variable_get(:time) else AttributeSerializerFactory.for(klass, attr).deserialize(val) end end def serialize(attr, val) AttributeSerializerFactory.for(klass, attr).serialize(val) end三个要点enum 字符串原样保留defined_enums第 27-29 行收集klass.defined_enums。若某属性是 enum 且当前值已是字符串反序列化时直接返回而不做映射——因为 PaperTrail 4 起就把 enum 的字符串形态写入object_changes此时数据库里存的是draft、published这类标签而不是整数。Rails 7 时间类型兜底ActiveRecord::Type::Time::Value在 Rails 7.0 上经工厂反序列化会抛委托错误对应 rails/rails#43966 问题因此这里直接读取其内部time实例变量绕过 bug。其余情况一律走工厂serialize与deserialize都通过AttributeSerializerFactory.for(klass, attr)拿到可能是替换后的Active Record 序列化器。3.4 工厂与 PostgreSQL 数组特殊处理AttributeSerializerFactory.forattribute_serializer_factory.rbdef for(klass, attr) active_record_serializer klass.type_for_attribute(attr) if ar_pg_array?(active_record_serializer) TypeSerializers::PostgresArraySerializer.new( active_record_serializer.subtype, active_record_serializer.delimiter ) else active_record_serializer end end注释解释了动机部分 Active Record 序列化器的返回值不适合直接写入文本列。典型就是 PostgreSQL 数组类型AR 的OID::Array序列化结果是一段带花括号与分隔符的字符串如{1,2,3}若直接YAML.dump进object文本列会得到冗余且难以查询的数据。因此工厂检测到属性是ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array的实例时替换为PostgresArraySerializer。PostgresArraySerializerpostgres_array_serializer.rb的策略是serialize(array)原样返回数组写入 JSON 列时由 JSON 类型负责文本化deserialize(array)若遇到字符串则说明是 Rails 5.0.2 时代序列化的历史数据此时调用ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(subtype, delimiter).deserialize(array)做兼容解析否则原样返回。这一行为在 spec/paper_trail/attribute_serializers/object_attribute_spec.rb 中有专门测试仅在ENV[DB] postgres时运行普通数组[1,2,3]序列化后仍是[1,2,3]字符串{1,2,3}能被反序列化为[1, 2, 3]数组元素为时间对象时往返保持一致。四、读取链路changeset 与 reify 如何反序列化4.1Version#changesetchangesetversion_concern.rb把object_changes还原为属性名 → [变更前, 变更后]的哈希其底层load_changeset第 317-345 行流程若配置了object_changes_adapter且其响应load_changeset直接交给适配器否则object_changes_deserialized第 351-364 行先行反序列化json列直接使用 AR 自动解析的结果text列则用PaperTrail.serializer.load当 YAML 因Psych::Exception加载失败时给出警告并返回{}只要item非 nildestroy 事件下 item 为 nil就调用AttributeSerializers::ObjectChangesAttribute.new(item.class).deserialize(changes)把每个[before, after]值对经过CastAttributeSerializer#deserialize还原为类型化值。4.2Version#reifyreify第 269-275 行调用::PaperTrail::Reifier.reify其内部reify_attributesreifier.rb先执行AttributeSerializers::ObjectAttribute.new(model.class).deserialize(attrs)再逐属性赋值。reifier 的注释第 85-90 行特别提醒ObjectAttribute#deserialize会返回 enum 映射后的值而在 Rails 5 中[]使用列定义的整数类型 caster可能把通常是字符串的enum 值转成0而非正确值——这正是该模块必须介入反序列化的原因之一。4.3 双向对称性可以发现模块刻意保持了写入/读取的对称ObjectAttribute#serialize ↔ #deserialize、ObjectChangesAttribute#serialize ↔ #deserialize共用同一套alter骨架与CastAttributeSerializer。因此只要模型定义与序列化器稳定reify出来的记录就能与历史版本保持一致。五、关键设计细节与兼容性保障5.1 Rails 7.0 内置加密属性两个列级序列化器初始化时都会取model_class.encrypted_attributes.map(:to_s)object_attribute.rb。在json/jsonb列场景下alter只对加密属性走类型转换因为加密值必须在写入前按 AR 的加密类型序列化而普通属性交给 JSON 列类型即可。5.2text列与整体序列化器PaperTrail.serializer需要区分两层属性序列化器本文主角处理值 → 类型化值整体序列化器PaperTrail.serializer处理哈希 → 文本默认是PaperTrail::Serializers::YAML也支持 JSONlib/paper_trail/serializers/json.rb以及用户自定义序列化器。当versions表的列是text时recordable_object/recordable_object_changesevents/base.rb会调用PaperTrail.serializer.dump把整个哈希转成 YAML/JSON 字符串。5.3 YAML 反序列化安全safe_load关于PaperTrail.serializer.loaddoc/pt_13_yaml_safe_load.md 记录了安全演变PT 13.0.0 起在 Rails 7.0 上默认使用YAML.safe_load除非显式设置ActiveRecord.use_yaml_unsafe_loadPT 14.0.0 将这一保护扩展到 Rails 6更早版本使用unsafe_load。具体判断逻辑见 serializers/yaml.rb。此项变化仅影响object/object_changes列为text且使用 YAML 序列化器的用户使用 JSON 序列化器或json(b)列的用户不受影响。若必须继续使用 YAML官方建议按需配置白名单::ActiveRecord.use_yaml_unsafe_load false ::ActiveRecord.yaml_column_permitted_classes [ ::ActiveRecord::Type::Time::Value, ::ActiveSupport::TimeWithZone, ::ActiveSupport::TimeZone, ::BigDecimal, ::Date, ::Symbol, ::Time ]长期建议则是优先迁移到json(b)列或 JSON 序列化器。5.4 自定义属性序列化器ARserializecoder反序列化链路同样尊重 Active Record 的serialize :attr, coder:机制。测试套件中的 person.rb 是一个完整范例Person定义TimeZoneSerializerdump把TimeZone对象存成时区名load用Time.find_zone还原并通过serialize :time_zone, coder: TimeZoneSerializer.newRails 7.1 用关键字形式旧版用位置参数注册spec/models/person_spec.rb 随后验证其在 PaperTrail 快照中的往返正确性。这印证了 version_concern.rb 中deserialization 步骤负责自定义属性序列化器的注释version_concern.rb。5.5 自定义整体序列化器示例仓库还提供了整体序列化器的自定义范例 spec/support/custom_json_serializer.rb它扩展PaperTrail::Serializers::JSON在load时剔除空键值、在dump时剔除空元素从而让object列更精简。自定义整体序列化器与本文的属性序列化器分层协作前者决定哈希如何成文后者决定值如何类型化。六、测试验证与可观测手段object_attribute_spec.rb仅当ENV[DB] postgres时验证数组列的序列化/反序列化、旧数据兼容与时间对象往返。person_spec.rb验证自定义TimeZoneSerializer在 PaperTrail 写入与读取两侧都生效。serializer_spec.rb、json_spec.rb、yaml_spec.rb覆盖整体序列化器的 dump/load 与安全加载分支。阅读源码时建议从两条主链路入手写入看 events/base.rb 的object_attrs_for_paper_trail/serialize_object_changes读取看 version_concern.rb 的load_changeset与 reifier.rb 的reify_attributes两个方向最终都会汇聚到本文介绍的四个类上。七、小结PaperTrail 的属性序列化器模块解决了三个核心问题类型正确性借助 Active Record 的类型系统type_for_attribute 工厂让写入versions表的属性值与模型定义一致包括 enum 字符串、时间对象等特殊类型存储适配根据object/object_changes列是text还是 PostgreSQLjson(b)选择不同的处理路径并对 PG 数组历史数据做向后兼容安全与可扩展默认 YAML 序列化器已升级到safe_load同时通过serialize coder与自定义整体序列化器保留了两层扩展点让应用能够按需定制快照内容。赞分享后端【免费下载链接】paper_trailTrack changes to your rails models项目地址https://gitcode.com/gh_mirrors/pa/paper_trail点击查看免费下载相关推荐3分钟部署通达信缠论指标让技术分析从复杂到简单3分钟部署通达信缠论指标让技术分析从复杂到简单 还在为理解复杂的缠论结构而苦恼吗面对K线图中的分型、笔、线段和中枢你是否感到无从下手通达信缠论可视化分析金融科技Apereo CAS 属性释放同意Attribute Release Consent的 LDAP 存储方案配置、JSON 序列化与源码级原理Apereo CAS 属性释放同意Attribute Release Consent的 LDAP 存储方案配置、JSON 序列化与源码级原理 导读 本文聚后端认证鉴权单点登录10 分钟上手 skills让 AI 画算法艺术、做 Canvas 设计的完整指南10 分钟上手 skills让 AI 画算法艺术、做 Canvas 设计的完整指南 skills3/skills 仓库GitHub 推荐项目精选收录把一批人工智能AI 技能AI 评测创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考