Rails加载User时自动触发关联Location查询问题(与Devise相关)
加载User记录时自动触发Location关联查询的原因?
环境信息
- Ruby版本:2.4.4
- Rails版本:5.1.4
- Devise版本:4.4.3
问题现象
我在应用里遇到一个奇怪的问题:每次加载User记录时,它关联的Location记录都会被自动加载。但只要注释掉User模型里的devise配置行,这个现象就消失了。而且两个模型都没定义默认作用域,这就让我很困惑了。
相关模型代码
User模型
class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable has_one :location, as: :owner end
Location模型
class Location < ApplicationRecord belongs_to :owner, polymorphic: true, required: false end
控制台查询日志
在Rails控制台执行User.last时,会触发两条SQL查询:
User Load (1.5ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL ORDER BY "users"."id" DESC LIMIT $1 [["LIMIT", 1]]
紧接着自动执行Location的查询:
Location Load (6.8ms) SELECT "locations".* FROM "locations" WHERE "locations"."owner_id" = $1 AND "locations"."owner_type" = $2 LIMIT $3 [["owner_id", 9], ["owner_type", "User"], ["LIMIT", 1]]
原因分析
根据你的描述和代码,我判断问题出在Devise的trackable模块上。具体来说:
当你在控制台执行User.last时,控制台会自动调用返回对象的inspect方法来打印详细信息。而Devise的trackable模块会给User模型添加几个登录统计相关的属性(比如sign_in_count、last_sign_in_at),在你使用的Rails 5.1.4和Devise 4.4.3的组合中,当inspect遍历模型的属性和关联时,会意外触发polymorphic has_one关联的自动加载——这就是为什么你会看到两条查询。
另外,你的User模型包含deleted_at字段(从查询日志里能看到软删除的条件),虽然你说没有默认作用域,但软删除逻辑可能和Devise的模块产生了额外的交互,加重了这个问题。
验证方法
你可以快速验证这个猜测:临时从Devise配置里移除trackable,然后再执行User.last:
class User < ApplicationRecord # 暂时去掉trackable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable has_one :location, as: :owner end
如果此时只触发User的查询,那就坐实了trackable是问题的诱因。
解决方法
根据你的需求,有两种解决方式:
- 不需要trackable功能:直接从Devise配置里删掉
:trackable模块,问题就彻底解决了。 - 需要保留trackable:
- 控制台里避免直接打印用户对象:比如改成
user = User.last; nil,这样控制台不会调用inspect,也就不会触发关联加载。 - 自定义User模型的
inspect方法,排除关联字段:class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable has_one :location, as: :owner def inspect attr_str = attributes.except("location").map { |k, v| "#{k}: #{v.inspect}" }.join(", ") "#<User #{attr_str}>" end end
- 控制台里避免直接打印用户对象:比如改成
内容的提问来源于stack exchange,提问作者Dan
相关产品推荐
相关产品推荐

