Rails应用JSON序列化时如何过滤对象关联并隐藏活跃状态?
问题解答
先明确一下场景:
- Foo 和 Bar 是一对多关联(
Foo has_many :bars) - Bar 用
is_active属性区分活跃/非活跃状态 - Foo 已经有了
active_bars方法,专门获取活跃的 Bar - 现在做 Foo 的 JSON API,要求只返回活跃的 Bar,而且不能让客户端知道“活跃/非活跃”这个概念
咱们要解决的核心问题是:过滤逻辑该放在哪儿、怎么实现,还有响应里要用什么键名?
先说说现有的几个思路的问题
用
as_json指定active_bars方法- 做法:
foo.as_json({methods: [:active_bars]}) - 问题:响应里会出现
active_bars这个键,直接把“活跃”的概念暴露给客户端了,不符合要求。
- 做法:
控制器里写工具方法映射键名
- 做法:在控制器里自定义方法,把
active_bars的结果转成bars键返回 - 问题:以后其他API端点要序列化Foo的时候,必须记得用这个工具方法,很容易忘,维护起来麻烦。
- 做法:在控制器里自定义方法,把
重写
Foo.as_json固定返回活跃Bar到bars键- 做法:修改Foo模型的
as_json,不管啥情况都把active_bars放到bars里 - 问题:默认的JSON结构和实际数据库的关联结构不一样了,如果以后有场景需要同时返回所有Bar和活跃Bar,根本没法处理。
- 做法:修改Foo模型的
重写
Foo.as_json加自定义选项- 做法:给
as_json加个自定义选项:foo_active_bars_only,指定的时候才把活跃Bar映射到bars - 问题:用了Rails原生API之外的自定义选项,不符合框架规范,其他开发者接手的时候容易懵。
- 做法:给
推荐两个更优的方案
方案一:用序列化器(比如Active Model Serializers)
Rails里处理API响应格式,最推荐用专门的序列化工具,比如Active Model Serializers(AMS):
- 给Foo建一个序列化器
FooSerializer - 在序列化器里定义
bars关联,直接用active_bars的结果:
class FooSerializer < ActiveModel::Serializer has_many :bars def bars object.active_bars end end
- 控制器里直接用序列化器返回:
render json: foo
- 好处:
- 响应里只有
bars键,完全隐藏了“活跃”的概念 - 序列化逻辑都集中在序列化器里,不用改模型的
as_json,也不用在控制器重复写代码 - 以后如果有需要返回所有Bar的场景,直接新建一个序列化器(比如
FooWithAllBarsSerializer)就行,互不影响
- 响应里只有
方案二:在模型里定义过滤后的关联
在Foo模型里新增一个专门用于API的关联,默认过滤活跃Bar:
class Foo < ApplicationRecord has_many :bars has_many :active_bars, -> { where(is_active: true) }, class_name: 'Bar' # 新增API专用关联 has_many :api_bars, -> { where(is_active: true) }, class_name: 'Bar' end
然后用序列化器把这个关联映射成bars键:
class FooSerializer < ActiveModel::Serializer has_many :api_bars, key: :bars end
- 好处:
- 关联逻辑封装在模型里,符合Rails的设计思路
- 可以灵活切换:默认的
bars返回所有Bar,api_bars返回活跃的 - 响应里显示的是
bars,客户端完全不知道内部的实现细节
响应键名建议
不管用哪种方案,响应里都应该用**bars**作为键名,这样客户端完全感知不到“活跃/非活跃”的存在,完美符合需求。
内容的提问来源于stack exchange,提问作者Mike Vosseller
相关产品推荐
相关产品推荐

