Rails中关联has_many时如何加载特定关联记录并筛选用户
用户(User)与组织(Organisation)为多对多关系,UserOrganisation作为连接模型,存储特定用户在特定组织中的相关数据。
模型定义如下:
class Organisation < ApplicationRecord has_many :user_organisations, dependent: :destroy has_many :users, through: :user_organisations end class User < ApplicationRecord has_many :user_organisations, dependent: :destroy has_many :organisations, through: :user_organisations end class UserOrganisation < ApplicationRecord belongs_to :user belongs_to :organisation end
需要获取某一特定组织下的所有用户,排除super_user属性为true的用户,同时预加载对应的UserOrganisation连接记录。
目前使用的代码如下:
class User < ApplicationRecord scope :super_users, -> { where(super_user: true) } end @organisation.users.excluding(User.super_users)
希望能够直接引用对应UserOrganisation(即UserOrganisation.where(user_id: user.id, organisation_id: @organisation.id))中的字段值,询问是否有更合适的写法,还是必须使用@organisation.user_organisations.joins(:users).merge(User.not_super_users)这种方式?
写法一:从用户维度查询并精准预加载连接记录
先给User模型补充非超级用户的Scope,再在查询时直接限定预加载的连接记录为当前组织:
class User < ApplicationRecord scope :super_users, -> { where(super_user: true) } scope :non_super_users, -> { where(super_user: false) } end # 查询组织下的非超级用户,同时只预加载当前组织对应的UserOrganisation记录 users = @organisation.users.non_super_users.includes(:user_organisations).where(user_organisations: { organisation_id: @organisation.id })
这种写法拿到的每个user对象,其user.user_organisations只会包含当前组织对应的那条连接记录,直接就能引用其中的字段。
写法二:从连接模型出发查询(更直接高效)
你提到的从user_organisations出发的写法其实非常合适,一次查询就能同时拿到连接模型和关联用户的数据,后续可以直接访问双方字段:
# 基于补充的non_super_users Scope查询 user_org_records = @organisation.user_organisations.joins(:user).merge(User.non_super_users).includes(:user) # 访问连接模型字段 user_org_records.first.specific_field_in_user_organisation # 访问关联用户信息 user_org_records.first.user
这种方式避免了多余的数据加载,代码简洁且性能更优。
原写法的问题
你原来的@organisation.users.excluding(User.super_users)是先加载组织下所有用户,再在内存中过滤超级用户,数据量大时会浪费性能;而且预加载user_organisations时会加载用户所有组织的连接记录,后续还要额外筛选当前组织的,完全没必要。
内容的提问来源于stack exchange,提问作者Phil-6

