Ruby Hash中tap/delete与except的行为差异及测试异常原因咨询
冻结哈希在Ruby 2.6.6 + Rails 6.0.6.1中的行为差异解析
先明确几个基础事实:
- Ruby原生冻结哈希调用
delete会直接抛出RuntimeError,无法修改原对象; - Rails给Hash扩展了多个方法,包括对
delete的补丁,以及except方法; tap方法的核心作用是返回调用它的原对象,而非块内代码的返回值。
针对你提到的三种写法,逐一分析差异原因:
1. result = source.tap { |s| s.delete(:key) }测试通过的原因
你提到这个写法会返回移除:key的source副本,但实际上tap的返回值始终是原对象source(冻结状态)。如果测试用例通过,大概率是以下两种情况之一:
- 测试用例仅断言
result的内容与预期匹配,但实际上source本身并未被修改(这里存在逻辑矛盾,因为冻结哈希无法修改),可能是Rails 6.0.6.1的delete补丁在处理冻结哈希时,自动返回了修改后的副本,但你误把delete的返回值当成了tap的返回值; - 你的
source并非真正的冻结哈希(比如某些ActiveRecord属性哈希表面冻结,但内部有特殊处理),调用delete时实际修改了原对象,导致tap返回的原对象内容符合预期。
2. result = source.except(:key)测试失败的原因
Rails的except方法实现逻辑是:先创建原哈希的非冻结副本,再移除指定键并返回这个副本。如果你的测试用例期望result是冻结状态的哈希,这个非冻结的副本就会不符合断言要求,导致测试失败。
3. result = source.dup.tap { |s| s.delete(:key) }测试失败的原因
dup方法会生成原哈希的非冻结副本,tap块内删除键后返回的就是这个非冻结副本。和except的情况一致,如果测试用例要求结果是冻结哈希,这个写法就会失败。
内容的提问来源于stack exchange,提问作者Ruby Racer
相关产品推荐
相关产品推荐

