RailsのAPIモードで、Controllersのrender jsonを親・子・孫・ひ孫という、深いリレーションをJSON形式でそれぞれネストし出力する方法のメモ。
Agenda
環境
- Rails6のAPIモードでJbuilderは不使用のためviewはなくController完結
- ControllerでJSONを標準で利用(render jsonで出力)
- 親・子・孫・ひ孫のアソシエーションを設定。きれいな4階層の世代関係ではなく2つの塊でリレーション
- 1つ目の塊:親ー子
- 2つ目の塊:子ー孫ーひ孫
設定
(親モデル)api/app/models/document_group.rb
class DocumentGroup < ApplicationRecord
belongs_to :user
has_many :document_agenda_items, -> { order('order_number') }, dependent: :destroy
scope :ordered, -> {
order('name')
}
end
(子モデル)api/app/models/document_agenda_item.rb
class DocumentAgendaItem < ApplicationRecord
belongs_to :document_group
belongs_to :agenda_item
has_one :agenda_details, through: :agenda_item
scope :ordered, -> {
order('order_number')
}
end
(孫モデル)api/app/models/agenda_item.rb
class AgendaItem < ApplicationRecord
belongs_to :agenda_group
has_many :agenda_details, dependent: :destroy
has_one :document_agenda_item, dependent: :destroy
scope :ordered, -> {
order('order_number')
}
end
(ひ孫モデル)api/app/models/agenda_detail.rb
class AgendaDetail < ApplicationRecord
belongs_to :agenda_item
end
api/app/controllers/v1/document_groups_controller.rb
class V1::DocumentGroupsController < ApplicationController
def show_relations
@document_groups = DocumentGroup.ordered
render json:
@document_groups, # 親モデル
include: {
:document_agenda_items => { # 子モデル
:include => {
:agenda_item => { # 孫モデル
include: :agenda_details # ひ孫モデル
}
}
}
}
end
end
アソシエーションを設定して「include」を利用すると取得できる。
出力結果
localhost:3000/v1/document_groups/{{DOCUMENT_GROUP_ID}}/show_relations
[
{
"id": 1,
"user_id": 1,
"name": "SFA PJ",
"created_at": "2021-08-14T05:56:07.534Z",
"updated_at": "2021-08-14T05:56:07.534Z",
"document_agenda_items": [
{
"id": 3,
"document_group_id": 1,
"agenda_item_id": 10,
"order_number": 1,
"created_at": "2021-09-18T09:06:53.429Z",
"updated_at": "2021-09-18T09:06:53.429Z",
"indent_level": 1,
"agenda_item": {
"id": 10,
"agenda_group_id": 1,
"name": "Project Phases",
"order_number": 2,
"created_at": "2021-09-05T07:21:39.276Z",
"updated_at": "2021-09-05T07:21:39.276Z",
"indent_level": null,
"agenda_details": []
}
},
{
"id": 1,
"document_group_id": 1,
"agenda_item_id": 1,
"order_number": 2,
"created_at": "2021-08-14T05:57:13.772Z",
"updated_at": "2021-09-15T11:00:40.122Z",
"indent_level": 2,
"agenda_item": {
"id": 1,
"agenda_group_id": 1,
"name": "Overview",
"order_number": 1,
"created_at": "2021-08-14T05:42:50.929Z",
"updated_at": "2021-08-14T05:42:50.929Z",
"indent_level": null,
"agenda_details": [
{
"id": 1,
"agenda_item_id": 1,
"body": "This document aims to give a detailed description on how the applications that will be implemented are designed. This is done by using high-level, component and more detailed class charts.",
"created_at": "2021-08-14T05:47:56.855Z",
"updated_at": "2021-08-14T05:47:56.855Z"
},
{
"id": 12,
"agenda_item_id": 1,
"body": "The Project will be divided into 6 phases.",
"created_at": "2021-09-14T15:06:34.994Z",
"updated_at": "2021-09-14T15:06:34.994Z"
},
{
"id": 5,
"agenda_item_id": 1,
"body": "コレクションをインスタンスから取得できます。",
"created_at": "2021-09-05T07:27:47.658Z",
"updated_at": "2021-09-15T10:54:58.337Z"
}
]
}
},
{
"id": 2,
"document_group_id": 1,
"agenda_item_id": 8,
"order_number": 3,
"created_at": "2021-09-18T09:06:16.287Z",
"updated_at": "2021-09-18T09:06:16.287Z",
"indent_level": 2,
"agenda_item": {
"id": 8,
"agenda_group_id": 9,
"name": "Sample Agenda Item",
"order_number": 1,
"created_at": "2021-08-15T14:56:33.134Z",
"updated_at": "2021-08-15T14:56:33.134Z",
"indent_level": null,
"agenda_details": [
{
"id": 3,
"agenda_item_id": 8,
"body": "9つの知識エリアをもとにプロジェクト管理を推進します。",
"created_at": "2021-08-15T15:08:04.410Z",
"updated_at": "2021-08-15T15:08:22.285Z"
}
]
}
}
]
},
{
"id": 4,
"user_id": 1,
"name": "SFA PJ Requirements Definition Document",
"created_at": "2021-09-05T07:28:04.673Z",
"updated_at": "2021-09-05T07:28:04.673Z",
"document_agenda_items": []
},
{
"id": 2,
"user_id": 1,
"name": "Webシステム開発RFP",
"created_at": "2021-08-15T15:09:16.221Z",
"updated_at": "2021-08-15T15:09:28.699Z",
"document_agenda_items": [
{
"id": 8,
"document_group_id": 2,
"agenda_item_id": 9,
"order_number": 1,
"created_at": "2021-09-18T10:28:38.363Z",
"updated_at": "2021-09-18T10:28:38.363Z",
"indent_level": 1,
"agenda_item": {
"id": 9,
"agenda_group_id": 1,
"name": "Loadmap",
"order_number": 2,
"created_at": "2021-09-05T07:21:23.096Z",
"updated_at": "2021-09-15T10:50:29.379Z",
"indent_level": 2,
"agenda_details": [
{
"id": 4,
"agenda_item_id": 9,
"body": "The Project will be divided into 6 phases.",
"created_at": "2021-09-05T07:22:30.368Z",
"updated_at": "2021-09-05T07:22:30.368Z"
}
]
}
}
]
}
]
その他
親・子・孫の場合
3世代のリレーションにしたい場合
def show_relations
@document_groups = DocumentGroup.ordered
render json:
@document_groups,
include: {
:document_agenda_items => {
include: :agenda_item
}
}
end
根本的な解決
本件はあくまで暫定策になるため、プロトタイピングなどを除き、利用は非推奨(個人的な意見)。根本的な解決は以下が必要。
- リレーションが深すぎる場合はDB設計の見直し
- Fat Controllerになってしまい汎用性もなくなりバグ誘発の可能性あり。Fat Modelにするため、Modle側にアソシエーションとメソッドを定義して、それをJbuilderのViews側で出力する方法を推奨(おそらく一般的にはこの方法)
参考情報
Stack Overflow - Ruby on Rails 5.0.2 - Multiple Nested Json Render