新闻详情

新闻详情

首页 / 资讯中心 / 详情

factory_bot 回调执行顺序全解析:Global、Inherited、Factory 与 Trait 的触发次序

发布时间:2026/9/26 10:11:26来源:尧图网络
factory_bot 回调执行顺序全解析:Global、Inherited、Factory 与 Trait 的触发次序
测试开发工具【免费下载链接】factory_botA library for setting up Ruby objects as test data.项目地址https://gitcode.com/gh_mirrors/fa/factory_bot点击查看免费下载回调Callback是 factory_bot 在对象构建、保存、打桩等生命周期节点上挂接自定义逻辑的核心机制。本篇技术指南以官方文档 callback_order.md 为骨架结合当前仓库源码与验收测试完整讲解before(:all)、before(:build)、after(:build)、after(:all)等回调事件在同一时刻触发时的精确执行顺序并回答两个高频实战问题Trait 回调为何总是排在工厂回调之后、按请求顺序执行继承父子工厂的回调又如何层层叠加读完后你将能准确预判任意回调组合的执行结果避免因回调次序导致的测试数据污染与状态错乱。回调事件总览在深入顺序之前先明确 factory_bot 提供的六种策略级回调及其触发时机来源callbacks/summary.mdCallback触发时机before(:all)任何策略含自定义策略开始构造对象之前before(:build)工厂构建对象之前经由FactoryBot.build或FactoryBot.createafter(:build)工厂构建对象之后经由FactoryBot.build或FactoryBot.createbefore(:create)工厂保存对象之前经由FactoryBot.createafter(:create)工厂保存对象之后经由FactoryBot.createafter(:stub)工厂打桩对象之后经由FactoryBot.build_stubbedafter(:all)任何策略完成之后含自定义策略核心规则回调按四层次序执行当某个回调事件如after_build或before_all被触发时所有绑定到该事件的回调按以下顺序执行Global callbacks全局回调Inherited callbacks继承自父工厂的回调Factory callbacks当前工厂自身定义的回调Trait callbacks按调用时请求的顺序执行这条规则对所有回调事件一视同仁。下面用两个文档原例分别演示「无继承」与「有继承」两种场景下的完整输出。简单工厂示例Trait 回调按请求顺序排在最后FactoryBot.define do before(:all) { puts Global before(:all) } after(:all) { puts Global after(:all) } factory :user do before(:all) { puts User before(:all) } after(:all) { puts User after(:all) } before(:build) { puts User before(:build) } after(:build) { puts User after(:build) } trait :trait_a do before(:build) { puts Trait-A before(:build) } after(:build) { puts Trait-A after(:build) } end trait :trait_b do before(:build) { puts Trait-B before(:build) } after(:build) { puts Trait-B after(:build) } end end end build(:user, :trait_b, :trait_a)执行结果与文档一致# 1. Global before(:all) # 2. User before(:all) # 3. User before(:build) # 4. Trait-B before(:build) # 5. Trait-A before(:build) # 6. User after(:build) # 7. Trait-B after(:build) # 8. Trait-A after(:build) # 9. Global after(:all) # 10. User after(:all)三个关键观察点全局回调最先、最后各一响before(:all)与after(:all)是生命周期首尾事件全局版本始终占据该事件序列的两端。Trait 回调在工厂回调之后尽管before(:build)与after(:build)都是对象构建阶段的钩子但工厂自身的回调先跑Trait 回调统一殿后。Trait 顺序等于请求顺序调用时写作build(:user, :trait_b, :trait_a)输出就是Trait-B先于Trait-A与书写顺序完全一致注意这与文档旧版本中Trait 在工厂回调之前的历史行为不同当前仓库以测试为准。继承工厂示例父工厂回调插在全局与子工厂之间FactoryBot.define do before(:all) { puts Global before(:all) } before(:build) { puts Global before(:build) } after(:build) { puts Global after(:build) } after(:all) { puts Global after(:all) } factory :parent do before(:all) { puts Parent before(:all) } before(:build) { puts Parent before(:build) } after(:all) { puts Parent after(:all) } after(:build) { puts Parent after(:build) } trait :trait_a do before(:build) { puts Trait-A before(:build) } after(:build) { puts Trait-A after(:build) } end factory :child do before(:all) { puts Child before(:all) } before(:build) { puts Child before(:build) } after(:build) { puts Child after(:build) } after(:all) { puts Child after(:all) } trait :trait_b do before(:build) { puts Trait-B before(:build) } after(:build) { puts Trait-B after(:build) } after(:all) { puts Trait-B after(:all) } end trait :trait_c do before(:build) { puts Trait-C before(:build) } after(:build) { puts Trait-C after(:build) } before(:all) { puts Trait-C before(:all) } end end end end build(:child, :trait_c, :trait_a, :trait_b)执行结果# 1. Global before(:all) # 2. Parent before(:all) # 3. Child before(:all) # 4. Trait-C before(:all) # 5. Global before(:build) # 6. Parent before(:build) # 7. Child before(:build) # 8. Trait-C before(:build) # 9. Trait-A before(:build) # 10. Trait-B before(:build) # 11. Global after(:build) # 12. Parent after(:build) # 13. Child after(:build) # 14. Trait-C after(:build) # 15. Trait-A after(:build) # 16. Trait-B after(:build) # 17. Global after(:all) # 18. Parent after(:all) # 19. Child after(:all) # 20. Trait-B after(:all)这个例子揭示了继承场景下的完整层次继承回调紧跟全局回调Global → Parent → Child父工厂回调天然先于子工厂回调构成越祖先越靠前的链式次序after(:all)方向同理。Trait 来自多个层级仍按请求顺序:trait_a定义在父工厂:parent:trait_b、:trait_c定义在子工厂:child请求顺序为:trait_c, :trait_a, :trait_b输出便严格按此排列——trait 归属哪个工厂不影响其在队列中的相对位置。Trait 的before/after组内次序不变例如before(:all)只绑定到Trait-C它排在Child before(:all)之后after(:all)只绑定到Trait-B它排在Child after(:all)之后。源码级原理这份顺序从何而来1. 回调的注册与事件名映射在 lib/factory_bot/definition.rb 中before/after/callback三个 DSL 方法统一把事件名转成符号并压入callbacksdef before(*names, block) callback(*names.map { |name| before_#{name} }, block) end def after(*names, block) callback(*names.map { |name| after_#{name} }, block) end def callback(*names, block) names.each do |name| add_callback(Callback.new(name, block)) end end因此before(:build)实际注册的事件名是:before_buildafter(:all)注册为:after_all。事件触发时由 CallbacksObserver 按callback.name name过滤出同一事件的所有回调再逐一run。2. 四层顺序的组装aggregate_from_traits_and_self顺序的核心藏在 Definition#callbacks 与 Definition#aggregate_from_traits_and_selfdef callbacks aggregate_from_traits_and_self(:callbacks) { callbacks } end def aggregate_from_traits_and_self(method_name, block) compile [ base_traits.map(method_name), instance_exec(block), additional_traits.map(method_name) ].flatten.compact end返回值依次是base_traits——定义时通过factory :child, traits: [...]或inherit_traits注入的 trait多为父工厂 trait其回调排在最前自身callbacks——当前工厂块内直接写的回调居中additional_traits——运行时通过build(:child, :trait_c, :trait_a, :trait_b)追加的 trait见 Factory#with_traits 的append_traits其回调按追加顺序排在最后。这正好解释了文档规则的后半段Factory callbacks 先于 Trait callbacksTrait 按请求顺序执行。3. 全局与继承的注入DefinitionHierarchy 继承链DefinitionHierarchy 把每个工厂编译成一个继承链上的类hierarchy_class Class.new(parent.hierarchy_class)factory.rb#L124-L125随后build_from_definition把该工厂的 callbacks 通过super() callbacks追加definition_hierarchy.rb#L11-L17。因此链的最顶端委托给Internaldelegate :callbacks, ..., to: Internal而Internal的before/after/callbacks最终委托给全局Configurationlib/factory_bot/internal.rb——这就是全局回调最先执行的机制父工厂hierarchy_class中已有的回调在super()中先返回子工厂回调追加在后——这就是继承回调先于工厂回调的机制。4. before_all / after_all 的触发点Factory#run 在策略执行前后各通知一次evaluation.notify(:before_all, nil) instance strategy.result(evaluation).tap(block) evaluation.notify(:after_all, instance)notify最终路由到CallbacksObserver#updatelib/factory_bot/callbacks_observer.rb所以before(:all)/after(:all)对整个构建流程只各触发一轮。5. 回调块参数一个实例还是两个Callback#run 根据块参数个数决定注入方式case block.arity when 1, -1, -2 then syntax_runner.instance_exec(instance, block) when 2 then syntax_runner.instance_exec(instance, evaluator, block) else syntax_runner.instance_exec(block) end一个参数注入当前构建/保存的实例如after(:build) { |user| ... }两个参数额外注入evaluator可读取 transient 属性与上下文如after(:build) { |user, context| ... }示例见 callbacks/summary.md无参数直接执行块。测试验证36 个回调的完整次序仓库的验收测试 spec/acceptance/callbacks_spec.rb 用一个「全局 父工厂 子工厂 三个 Trait」的组合一次性创建断言TestLog中恰好记录36 条回调并按before(:all) → before(:build) → after(:build) → before(:create) → after(:create) → after(:all)六个事件段分别校验每段都呈现global → parent → child → parent-trait-2 → child-trait → parent-trait-1的次序trait 按请求:parent_trait_2, :child_trait, :parent_trait_1排列。这正是本文两条规则在测试层面的完整印证。此外callbacks_spec.rb#L58-L61 验证了「同一实例上每个回调只执行一次」即便同一个 trait 被重复请求或以不同顺序请求回调也不会重复触发——这与CallbacksObserver用#{instance.object_id}-#{callback.object_id}记录已完成回调的去重逻辑callbacks_observer.rb#L25-L37完全吻合。实战要点小结预判次序的口诀每个事件内都是「全局 → 父工厂链 → 当前工厂 → 请求的 Trait按书写顺序」before事件正序、after事件同规则但方向相反after(:all)的 trait 回调仍排在最后。依赖回调结果的属性优先放在工厂自身回调中需要覆盖性清理或兜底逻辑时用 trait 回调并注意它晚于工厂回调执行。全局回调做跨工厂横切在FactoryBot.define顶层Internal→Configuration注册before(:all)/after(:all)可用于统一开关 ActiveRecord 模型回调如User.skip_callback(:create, :after, :send_welcome_email)见 callbacks/summary.md。回调块参数一个参数拿实例两个参数额外拿evaluator读取 transient 与上下文按需选用即可。掌握了这套顺序规则你在为 factory_bot 编写复杂嵌套工厂与多 Trait 组合时就能像阅读测试日志一样精确预测每一行输出的先后。赞分享测试开发工具【免费下载链接】factory_botA library for setting up Ruby objects as test data.项目地址https://gitcode.com/gh_mirrors/fa/factory_bot点击查看免费下载相关推荐CANN Runtime 示例解析基于 aclrtLaunchHostFunc 的 Stream HostFunc 回调下发与执行顺序控制CANN Runtime 示例解析基于 aclrtLaunchHostFunc 的 Stream HostFunc 回调下发与执行顺序控制 导读 本文以 CACANNAscend人工智能性能剖析系统编程Salt SLS Include 深度解析解析顺序、执行顺序与 Requisite 的协作机制Salt SLS Include 深度解析解析顺序、执行顺序与 Requisite 的协作机制 Salt 状态系统允许 SLS 文件通过 include 引用运维配置管理后端如何利用ABigSurvey快速掌握AI领域最新进展如何利用ABigSurvey快速掌握AI领域最新进展 ABigSurvey是一个包含1000多篇自然语言处理NLP和机器学习ML领域综述论文的项目它将上一篇Flux高级特性泛型精化与编译时安全检查完全指南下一篇Cheerio 文档加载指南load、loadBuffer、stringStream、decodeStream 与 fromURL 全解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

零基础部署 OpenClaw 自动化 AI:TaoToken 统一 Key 接入与免 Python 环境配置指南(含安装包) 2026/9/26 12:07:17

零基础部署 OpenClaw 自动化 AI:TaoToken 统一 Key 接入与免 Python 环境配置指南(含安装包)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
影栈实战:提示词工程进阶,让 AI 生图稳定输出品牌视觉的 6 个技巧 2026/9/26 12:07:17

影栈实战:提示词工程进阶,让 AI 生图稳定输出品牌视觉的 6 个技巧

影栈实战:提示词工程进阶,让 AI 生图稳定输出品牌视觉的 6 个技巧 凌晨两点,客户甩来一句「还是那个感觉,但再来二十张」。你盯着上一版刚跑顺的封面,心里清楚——AI 生图怕的不是画不出,是画不「稳」。同一…

阅读更多 →
【openclaw实用Skill】oracle 技能:用 CLI 打通 TypeScript 与 GPT-5.2 Pro 的配置骨架 2026/9/26 12:07:17

【openclaw实用Skill】oracle 技能:用 CLI 打通 TypeScript 与 GPT-5.2 Pro 的配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
设置 Windsurf 使用 VSCode 插件仓库安装更新插件:TaoToken 统一 Key 配置与验证 2026/9/26 12:07:17

设置 Windsurf 使用 VSCode 插件仓库安装更新插件:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
MiBeeNVR v0.13.0:存储路径与通道接入的深度可控架构 2026/9/26 12:07:15

MiBeeNVR v0.13.0:存储路径与通道接入的深度可控架构

1. 这不是普通NVR升级:它把录像的“主权”交还给用户MiBeeNvr v0.13.0 正式版预告里那句“录像存哪、接多少路,都归你管”,乍看是句宣传话术,实则戳中了安防系统部署中最常被忽视、却最影响长期可用性的两个硬骨头:存储…

阅读更多 →
Linux火焰图实战:从原理到Java性能排查 2026/9/26 12:07:08

Linux火焰图实战:从原理到Java性能排查

1. 为什么学了这么多年Linux,我还是强烈建议你掌握火焰图大概半年前,我接手一个Java线上服务,高峰期CPU直接飙到300%以上,top命令里看到的是java进程占满,但具体是哪段代码在烧CPU,完全抓瞎。按老套路&…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉