2017-3-22 更新,在 Rails 中还有更便捷的做法:
在 org 表中添加一个parent_id
字段,用以自引用。
org model 中这样写关联关系
belongs_to :parant_org, class_name: “Org”, foreign_key: “parent_id”
has_many :sub_orgs, class_name: “Org”, foreign_key: “parent_id”
层级隶属关系的业务场景在开发过程中很常见,譬如大型集团有总部、城市分公司、地区代理商及细化到门店这种不同层级形成的树状结构。最近系统接入了某个连锁美容品牌,其层级结构为:总部、地区分公司和门店。
设计理论
每个层级的个体在数据库表中都对应 orgs
记录,在层级关系中只有两种关系,一个 org 对于上层是子;对于上层为父 。hierarchies
表记录其在层级隶属关系中的角色并与之对应的上下级的记录。hierarchies
表有一个 type
字段,值为 Root、Parent、Sub,用以标记记录对于上下级关系的角色类型;hierarchy_id
字段与 orgs
表关联,标记当前的 org 记录;org_id
字段同样与 orgs
表的记录关联,用以找出当前 org
记录与type
字段对应的上级或下级的一个或多个 org
记录。
举个例子,新建一个位于根结点的总部 org 记录 A(id:1),隶属根系处于根结点不需在 hierarchies 表中添加记录。继续添加属于总部 A 的子节点 orgs 表记录 A_1(id:2) ,同时在 hierarchies 表添加三个记录,分别为对于 A_1 记录的父节点H_1(id: 1)和根节点H_2(id:2),另外一个记录是对于 A 记录的子节点H3(id: 3)。
| hierarchies 记录 | org_id | type | hierarchy_id|
| ——– | :—–: |:—-:|
| hierarchy_a| 1|Root|2|
| hierarchy_b|1|Parent|2|
| hierarchy_c|2|Sub|1|
当前 org 记录通过外键 hierarchy_id
与 hierarchies
表关联查找到与之对于的有层级关系的记录;然后通过 type
字段缩小范围,找到指定关系的记录,最后通过外键 org_id
找到对应隶属关系的记录在 orgs 表的记录。
结合 Rails 当表继承的实现
单表继承 基本概念在这里就不赘述了。
class Org < ApplicationRecord
has_one :root_hierarchy, class_name: "RootHierarchy", foreign_key: "hierarchy_id"
has_one :parent_hierarchy, class_name: "ParentHierarchy", foreign_key: "hierarchy_id"
has_many :sub_hierarchies, class_name: "SubHierarchy", foreign_key: "hierarchy_id"
......
end
RootHierarchy
, ParentHierarchy
, SubHierarchy
继承了 Hierarchy model
class Hierarchy < ApplicationRecord
belongs_to :org, class_name: "Org", foreign_key: "org_id"
belongs_to :hierarchy, class_name: "Org", foreign_key: "hierarchy_id"
......
end
新建根结点的 org 不需在 hierarchies 表新建对于的记录,新建 org 的子节点需要在 hierarchies 表同时新建三个记录。
parent_org = Org.find(hierarchy.to_i)
@object = Org.new(params[:org])
if @object.save
if parent_org
ActiveRecord::Base.transaction do
RootHierarchy.create!(org: parent_org.root_hierarchy.present? ? parent_org.root_hierarchy.org : parent_org, hierarchy: @object)
ParentHierarchy.create!(org: parent_org, hierarchy: @object)
SubHierarchy.create!(org: @object, hierarchy: parent_org)
end
end
列出某个某个的所有根、父、子节点
table.table.table-bordered.table-striped
- if model.root_hierarchy.present?
tr
td.prompt 根商户
td.inp= model.root_hierarchy.org.name
- if model.parent_hierarchy.present?
tr
td.prompt 父商户
td.inp= model.parent_hierarchy.org.name
- if model.sub_hierarchies.present?
tr
td.prompt 子商户
td.inp= model.sub_hierarchies.includes(:org).map { |e| "#{e.org.name}, " }