Rails中忽略未保存对象查最后记录及OFFSET行为疑问
问题1:更优的已保存记录查询方法
当然有!最直接且语义明确的方式,就是直接筛选出有id的记录——因为未保存的ActiveRecord对象id都是nil:
<% if @user.items.where.not(id: nil).last.issue_date + 2.years > Date.today %>
如果你觉得每次写where.not(id: nil)太啰嗦,还可以在模型里定义一个关联的作用域:
# app/models/user.rb class User < ApplicationRecord has_many :items do def persisted where.not(id: nil) end end end
之后在视图里就能用@user.items.persisted.last,可读性瞬间提升。
另外还有个小技巧:如果集合已经加载到内存,你也可以用Ruby的方法筛选:
<% if @user.items.select(&:persisted?).last&.issue_date + 2.years > Date.today %>
不过前两种方法更推荐,因为直接在数据库层面筛选,性能更好。
问题2:为什么offset(0)在ActiveRecord里有特殊效果?
这其实和PostgreSQL本身没关系,问题出在ActiveRecord的内存集合缓存机制上。
当你在控制器里执行@user.items.new时,ActiveRecord会把这个未保存的对象加入到@user.items的内存集合里。这时候你调用@user.items.last,ActiveRecord会直接从内存里的集合取最后一个元素——也就是那个未保存的空对象,自然会报错。
但当你加上offset(0)时,这个方法属于查询方法,会强制ActiveRecord放弃使用内存里的缓存集合,重新生成一条SQL语句去数据库查询。虽然PostgreSQL里OFFSET 0不影响结果,但这个调用触发了ActiveRecord的查询重建,所以最终取到的是数据库里真正的最后一条已保存记录。
总结一下:
@user.items.last:优先用内存里的集合(如果已经加载),否则查数据库@user.items.offset(0).last:强制触发数据库查询,完全忽略内存里的未保存对象
这就是为什么看起来offset(0)和PostgreSQL文档描述不符——本质是ActiveRecord的内存缓存逻辑导致的,和PostgreSQL的SQL行为无关。
内容的提问来源于stack exchange,提问作者oneWorkingHeadphone

