Rails中如何从Category模型获取所有关联的多态UserItem数据
嘿,这个场景我之前在项目里也遇到过!Rails默认的has_many :through确实不支持同时指定多个关联源,但我们有几种办法实现category.user_items直接获取所有关联的UserItem,我给你拆解一下:
首先先提个小细节:你的Listing和Group模型里的belongs_to :Category应该改成小写的belongs_to :category哦,Rails的关联命名遵循蛇形小写规则,大写的话可能会触发不必要的常量查找,容易出问题。
接下来是具体方案:
方案1:用Rails 6+的Union(推荐,最优雅)
如果你的项目用的是Rails 6及以上版本,可以利用ActiveRecord的union方法,先分别定义两种类型的UserItem关联,再合并它们:
class Category < ApplicationRecord has_many :listings has_many :groups # 分别定义两种类型的UserItem关联 has_many :listing_user_items, through: :listings, source: :user_items has_many :group_user_items, through: :groups, source: :user_items # 合并两个关联,返回ActiveRecord关系对象(支持链式调用) def user_items listing_user_items.union(group_user_items) end end
这个方案的好处是返回的是ActiveRecord::Relation,你可以继续链式调用where、order等方法,比如category.user_items.where(created_at: Date.today),非常灵活。
方案2:简单的实例方法(兼容所有Rails版本)
如果你的Rails版本比较旧,或者不需要链式调用,直接写一个实例方法查询即可:
class Category < ApplicationRecord has_many :listings has_many :groups def user_items UserItem.where( "(itemable_type = 'Listing' AND itemable_id IN (?)) OR (itemable_type = 'Group' AND itemable_id IN (?))", listing_ids, group_ids ) end end
这个写法简单直接,缺点是返回的是查询后的数组(如果调用load或者直接遍历),不能再链式添加查询条件。如果需要保持关系对象,Rails 5+可以用更简洁的写法:
def user_items UserItem.where( itemable: listings.or(groups) ) end
这个利用or方法合并两个关联的关系,同样返回ActiveRecord::Relation,支持后续链式操作。
方案3:自定义has_many关联(用finder_sql)
你也可以直接在Category里定义一个自定义的has_many关联,通过SQL语句来拼接结果:
class Category < ApplicationRecord has_many :listings has_many :groups has_many :user_items, class_name: 'UserItem', finder_sql: <<-SQL SELECT user_items.* FROM user_items JOIN listings ON listings.id = user_items.itemable_id AND user_items.itemable_type = 'Listing' WHERE listings.category_id = #{id} UNION SELECT user_items.* FROM user_items JOIN groups ON groups.id = user_items.itemable_id AND user_items.itemable_type = 'Group' WHERE groups.category_id = #{id} SQL end
这个方案的好处是直接通过category.user_items调用,但缺点是SQL语句硬编码,维护起来不如前两种方便,而且对预加载(eager loading)的支持不够友好。
总结
- 如果你用Rails 6+,优先选方案1,既优雅又灵活;
- 兼容旧版本的话,方案2的
itemable: listings.or(groups)写法最简洁; - 方案3适合特殊场景,但一般不推荐。
内容的提问来源于stack exchange,提问作者Abhilash

