如何在ActiveRecord中为Post集合关联查询Account对象?
如何在Post对象返回结果中包含关联的Account数据?
背景与问题描述
现有模型关系:Organization与Post为一对多(多态关联),Post属于Account。模型定义如下:
# Post class class Post < ApplicationRecord belongs_to :account, foreign_key: 'account_id' belongs_to :postable, polymorphic: true validates :content, length: { in: 1..500 } end # Organization class Organization < ApplicationRecord has_many :posts, as: :postable end
通过Organization.first.posts获取组织帖子时,返回的JSON结构仅包含accountId,不包含完整Account对象:
[ { "id": 101, "accountId": 50, "postableType": "Organization", "postableId": 3, "content": "Consequatur in illum vel voluptates.", "createdAt": "2022-11-23T04:57:45.271Z", "updatedAt": "2022-11-23T04:57:45.271Z" }, { "id": 102, "accountId": 46, "postableType": "Organization", "postableId": 3, "content": "Fugit totam minus et consequatur.", "createdAt": "2022-11-23T04:57:45.274Z", "updatedAt": "2022-11-23T04:57:45.274Z" } ]
期望每个Post对象中包含对应的Account对象,理想结构如下:
[ { "id": 101, "account": { "id": 46, "firstName": "Bob" }, "postableType": "Organization", "postableId": 3, "content": "Consequatur in illum vel voluptates.", "createdAt": "2022-11-23T04:57:45.271Z", "updatedAt": "2022-11-23T04:57:45.271Z" }, { "id": 102, "account": { "id": 46, "firstName": "Bob" }, "postableType": "Organization", "postableId": 3, "content": "Fugit totam minus et consequatur.", "createdAt": "2022-11-23T04:57:45.274Z", "updatedAt": "2022-11-23T04:57:45.274Z" } ]
尝试过Organization.first.posts.includes(:account)和Organization.first.posts.joins(:account),但返回结果未变化,询问实现需求的最佳方式。
解决方案
1. 序列化层显式包含关联数据(推荐)
includes(:account)只是预加载关联数据避免N+1查询,但不会自动将Account对象加入JSON输出,需要在序列化配置中指定:
场景1:使用Jbuilder
在对应的Jbuilder模板中,显式渲染account属性:
# app/views/posts/index.json.jbuilder json.array! @posts do |post| json.extract! post, :id, :postable_type, :postable_id, :content, :created_at, :updated_at json.account do json.extract! post.account, :id, :first_name end end
场景2:使用ActiveModel::Serializers
创建PostSerializer并指定包含account关联:
# app/serializers/post_serializer.rb class PostSerializer < ActiveModel::Serializer attributes :id, :postable_type, :postable_id, :content, :created_at, :updated_at belongs_to :account end # 对应的AccountSerializer(按需定义字段) class AccountSerializer < ActiveModel::Serializer attributes :id, :first_name end
在控制器中调用序列化:
def index organization = Organization.first @posts = organization.posts.includes(:account) render json: @posts, serializer: PostSerializer, include: :account end
2. 手动构建哈希(无序列化器时)
如果不想使用序列化库,可以手动构建包含Account的哈希数组,结合includes(:account)避免N+1:
organization = Organization.first posts = organization.posts.includes(:account).map do |post| post.as_json( only: [:id, :postable_type, :postable_id, :content, :created_at, :updated_at], include: { account: { only: [:id, :first_name] } } ) end # 此时posts就是包含account的哈希数组,可直接返回 render json: posts
3. 避免遍历单独查询(不推荐)
不要遍历每个Post单独查询Account,这会导致N+1查询问题(每个Post触发一次Account查询),严重影响性能。includes(:account)已经能批量预加载所有关联的Account,只需确保序列化时包含即可。
内容的提问来源于stack exchange,提问作者DrummerGenius
相关产品推荐
相关产品推荐

