从 Rails 6.1 升到 7 的时候,有仔细读过 ugprade guideline,但还是在部署之后遇到了一个很奇妙的错误,霸占了 Sentry…
ActionView::Template::Error
The :mode option must be one of [:all, :n_plus_one_only].
strict_loading
是 6.1 加的一个功能,用来检测 N+1 query 的,但是当时直接忽略了它,默认全局关闭,因为有用别的 gem 做了这件事。。
等到了 7 遇到这个错误,尝试 debug 还翻到了这个错误的源码所在地,总之非常不理解:
# activerecord/lib/active_record/core.rb
def strict_loading!(value = true, mode: :all)
unless [:all, :n_plus_one_only].include?(mode)
raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only] but #{mode.inspect} was provided."
end
@strict_loading_mode = mode
@strict_loading = value
end
最终没有在 stack overflow 找到原因,而是在 rails 的 PR 里看到了这条 comment
解决方法是:清理掉 rails 的缓存就可以了。
讲道理,修改代码后,缓存的模型和新代码不兼容,这本来是一件挺正常的事情,但是这个错误提示实在是让人费解。
猜测原因可能是在 view 里缓存了一部分 active records,而这些缓存是在升 7 之前缓存的…当从缓存里拿出来这些 ActiveRecord[] 的时候,它们没有 mode 这个属性,于是就报错了。
# Rails 6.1
u = User.new
u.valid?
# => false
u.errors.messages
# => {:email=>["can't be blank"]}
u.errors.messages.frozen?
=> false
# Rails 7.0.4
u = User.new
u.valid?
# => false
u.errors.messages.frozen?
=> true
详见这个 Issue Regression: Filtering a timestamp with timezone by date range 和这个 question
prefix = "brand"
# 各种Rails版本下的的PG
Model.where("friendly_id like '%?-%'", prefix).to_sql
=> "WHERE (friendly_id like '%'brand'-%')"
# Rails 6 下的MySQL
Model.where("friendly_id like '%?-%'", prefix).to_sql
=> "WHERE (friendly_id like '%brand-%')"
# Rails 7下的MySQL
Model.where("friendly_id like '%?-%'", prefix).to_sql
=> "WHERE (friendly_id like '%'brand'-%')"
于是就不得不这么写
Model.where("friendly_id like ?", "%#{prefix}-%")