rails7: category 无限分类实现
基于 ruby on rails 的无限分类实现
无限分类
一般分有多层分类。
技术点
所在分支:
feature/feat-category
- 数据表自关联
- 产生
tree
结构 - 缓存
Rails.cache.fetch
不常变化的数据 - repo: https://github.com/aric-notes/rails7-notes/tree/feature/feat-category
rails g model Category name:string parent:references --no-test-framework
修改 migration
文件
- 注意,这里我去掉了
null: false
,因为 category,主分类的 parent 就是 null - 添加了
foreign_key: { to_table: :categories }
,这里表明是自关联,所以需要添加这
class CreateCategories < ActiveRecord::Migration[7.1]
def change
create_table :categories do |t|
t.string :name
t.references :parent, foreign_key: { to_table: :categories }
t.timestamps
end
end
end
关于 migrations 这一块
网上看到的实现,这个应该更加的简洁,不用 reference,而直接用 parent_id 来。
- 可以处理
foreign_key
不能为nil 的尴尬- 可以不用处理 to_table 这个约束
- 参考: https://stackoverflow.com/questions/30091996/using-a-parent-child-self-join-in-activerecord
class create_category_table < ActiveRecord::Migration
def change
create_table :categories do |t|
t.integer :parent_id
end
end
end
class Category < ActiveRecord::Base
has_many :sub_categories, class: Category, foreign_key: :parent_id
belongs_to :parent, class: Category
end
@parent = Category.new
@child = @parent.sub_categories.build
调整 model
models/category.rb
解释一下内容
- 说明关系:
belongs_to :parent
/has_many :children
是一个主分类,有多个子分类 → 一对多 - class_name: 'Category' 指定了关联模型的类名是 Category。因为关联的模型是自身,所以需要显式指定类名。
- optional: true 表示这个关联是可选的,即一个分类可以没有父分类;在我的这个设计中, parent: null 表示,顶层分类
- dependent: :destroy 表示当父分类被销毁时,与之关联的子分类也会被销毁。
foreign_key: :parent_id
:当前 category 取 children 的时候,知道用哪个id(parent_id)
来查换
class Category < ApplicationRecord
belongs_to :parent, class_name: 'Category', optional: true
has_many :children, class_name: 'Category', foreign_key: :parent_id, dependent: :destroy
end
添加 seed
数据
root = Category.create(name: "Apparel Store")
shores = Category.create(name: "Shores", parent: root)
clothing = Category.create(name: "Clothing", parent: root)
限制层级
- 限制网站分类为2级
class Category < ApplicationRecord
# ... 省略10000行代码
validate :validate_hierarchy_depth
private
def validate_hierarchy_depth
max_depth = 2
if depth_of_hierarchy > max_depth
errors.add(:parent_id, "can't create more than #{max_depth} levels of hierarchy")
end
end
def depth_of_hierarchy
depth = 0
node = self
while node.parent.present?
depth += 1
node = node.parent
end
depth
end
end
root = Category.create(name: "Apparel Store")
shores = Category.create(name: "Shores", parent: root)
clothing = Category.create(name: "Clothing", parent: root)
men_shores = Category.create(name: "Men's Shores", parent: shores)
women_shores = Category.create(name:"Women's Shores", parent: shores)
# 这一行已经开始报错了
boy_shores = Category.create(name: "Boy's Shores", parent: men_shores)
生成一个大树
- 静态方法:
Category.tree
- 利用缓存优化 Category.tree 的 sql 调用
- 开启 Rails.cache,看这里: https://js.work/posts/0bf437de1dacc
class Category < ApplicationRecord
# ... 省略10000行代码
def self.tree
roots = Category.where(parent_id: nil).includes(:children)
roots.map { |root| build_category(root) }
end
def self.build_category(node)
{
id: node.id,
name: node.name,
children: node.children.map { |child| build_category(child) }
}
end
end
class Category < ApplicationRecord
# ... 省略10000行代码
def self.tree
Rails.cache.fetch('category_tree', expires_in: 1.year) do
roots = Category.where(parent_id: nil).includes(:children)
roots.map { |root| build_category(root) }
end
end
def self.build_category(node)
{
id: node.id,
name: node.name,
children: node.children.map { |child| build_category(child) }
}
end
end
优化 tree
这里不要这么多次
sql
查询,只有一次all
的查询,将build_tree
这个过程放在前端完成。
class Category < ApplicationRecord
# ... 省略10000行代码
def self.raw
Category.all.map { |category| { id: category.id, name: category.name, parent_id: category.parent_id } }
end
end
参考
- https://chat.zhile.io/c/340139a1-7c30-4d2f-9b82-7381f964882c
- https://medium.com/@asuthamm/self-join-in-rails-8e3fc99c0634
- https://devhaitham.medium.com/implementing-hierarchical-categories-in-rail-fd47f33b4bba
- https://stackoverflow.com/questions/30091996/using-a-parent-child-self-join-in-activerecord
- https://prograils.com/three-ways-iterating-tree-like-active-record-structures