Rails多模型树形结构建模失败求助:基于Closure Tree实现层级关系
问题描述
需要构建如下层级的树形数据结构:
Cohort |_____RecruitmentPhase |_______________Source
但因涉及多个带不同数据成员的模型,尝试用Closure Tree建模失败。现有模型类与数据库表结构如下:
a) RecruitmentPhase 模型与表结构
模型代码
class RecruitmentPhase < ApplicationRecord belongs_to :cohort, optional: true belongs_to :parent, class_name: 'Cohort', required: false , dependent: :destroy has_many :disease_phaseid_questionnaires, dependent: :destroy, foreign_key: :phase_id def phase_name_ret phase_name end end
表结构SQL
create_table "recruitment_phases", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t| t.integer "phase_id" t.string "phase_token" t.string "phase_name" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "parent_id" end
b) Cohort 模型与表结构
模型代码
class Cohort < ApplicationRecord end
表结构SQL
create_table "cohorts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "disease_code_id" t.string "disease_name" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.bigint "patient_id" t.integer "recruitment_phase_ids" t.index ["patient_id"], name: "index_cohorts_on_patient_id" end
c) Source 模型与表结构
模型代码
class Source < ApplicationRecord belongs_to :recruitment_phase, class_name: 'recruitment_phase', foreign_key: 'recruitment_phase_id' end
表结构SQL
create_table "sources", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t| t.integer "source_id" t.string "source_token" t.string "source_name" t.string "description" t.datetime "created_at", null: false t.datetime "updated_at", null: false end
解决方案
你当前的问题核心是误用了Closure Tree的适用场景,Closure Tree用于同一模型内的树形结构(如分类层级),而你要的是跨模型的一对多层级关联,用普通Active Record关联即可实现。具体修改步骤如下:
1. 修正模型关联
Cohort 模型
添加与RecruitmentPhase的一对多关联:
class Cohort < ApplicationRecord has_many :recruitment_phases, dependent: :destroy end
RecruitmentPhase 模型
移除错误的parent关联(关联到Cohort是错误的),添加与Source的一对多关联,并修正belongs_to :cohort的可选性:
class RecruitmentPhase < ApplicationRecord belongs_to :cohort, null: false # 确保每个招募阶段都属于一个队列 has_many :sources, dependent: :destroy has_many :disease_phaseid_questionnaires, dependent: :destroy, foreign_key: :phase_id def phase_name_ret phase_name end end
Source 模型
修正class_name的大小写错误(Rails模型类名首字母大写),并移除冗余的外键声明(默认外键就是recruitment_phase_id):
class Source < ApplicationRecord belongs_to :recruitment_phase, null: false end
2. 修正数据库表结构
需要创建三个迁移来调整表结构:
迁移1:修复RecruitmentPhases表
添加cohort_id外键,移除冗余的phase_id和错误的parent_id:
class FixRecruitmentPhasesTable < ActiveRecord::Migration[7.0] def change # 添加cohort外键 add_reference :recruitment_phases, :cohort, null: false, foreign_key: true # 移除不需要的字段 remove_column :recruitment_phases, :parent_id remove_column :recruitment_phases, :phase_id end end
迁移2:修复Sources表
添加recruitment_phase_id外键,移除冗余的source_id:
class FixSourcesTable < ActiveRecord::Migration[7.0] def change # 添加recruitment_phase外键 add_reference :sources, :recruitment_phase, null: false, foreign_key: true # 移除冗余主键字段 remove_column :sources, :source_id end end
迁移3:修复Cohorts表
移除冗余的recruitment_phase_ids字段(一对多关联不需要存储ID数组):
class RemoveRecruitmentPhaseIdsFromCohorts < ActiveRecord::Migration[7.0] def change remove_column :cohorts, :recruitment_phase_ids end end
3. 验证层级结构
修改完成后,即可通过以下方式访问层级数据:
- 获取某个队列下的所有招募阶段:
cohort.recruitment_phases - 获取某个招募阶段下的所有来源:
recruitment_phase.sources - 链式访问完整层级:
cohort.recruitment_phases.each { |rp| rp.sources }
内容的提问来源于stack exchange,提问作者raghu palakodety
相关产品推荐
相关产品推荐

