如何为同一Section下多类子模型全局使用acts_as_list?
解决方案:通过多态关联实现Section内全局排序
要在同一个Section下让Place、Note、Checklist共享全局位置序列,必须借助多态关系——因为acts_as_list默认仅针对单个模型维护位置,无法跨模型保证全局position的唯一性和排序逻辑。以下是具体实现步骤:
1. 新增中间模型与数据库迁移
创建一个SortableItem模型作为位置管理的中间层,通过多态关联绑定到各个子模型,同时关联Section。
生成迁移文件:
class CreateSortableItems < ActiveRecord::Migration[7.0] def change create_table :sortable_items do |t| t.integer :section_id, null: false t.integer :position, null: false t.references :sortable, polymorphic: true, null: false t.timestamps end # 确保同一Section下position唯一 add_index :sortable_items, [:section_id, :position], unique: true # 确保每个子模型仅对应一个SortableItem add_index :sortable_items, [:sortable_type, :sortable_id], unique: true end end
2. 定义模型关系
SortableItem模型
class SortableItem < ApplicationRecord acts_as_list scope: :section_id belongs_to :section belongs_to :sortable, polymorphic: true end
Section模型(修改后)
class Section < ApplicationRecord has_many :sortable_items, -> { order(position: :asc) }, dependent: :destroy # 通过多态关联间接获取各类型子项 has_many :places, through: :sortable_items, source: :sortable, source_type: "Place" has_many :notes, through: :sortable_items, source: :sortable, source_type: "Note" has_many :checklists, through: :sortable_items, source: :sortable, source_type: "Checklist" # 获取Section下全局排序的所有子项 def sorted_items sortable_items.includes(:sortable).map(&:sortable) end end
Place/Note/Checklist模型(以Place为例)
class Place < ApplicationRecord belongs_to :section has_one :sortable_item, as: :sortable, dependent: :destroy # 创建时自动生成对应的SortableItem after_create :create_sortable_item private def create_sortable_item SortableItem.create!(section: section, sortable: self) end end
Note和Checklist模型复制上述代码,替换类名即可。
3. 调整前端拖拽逻辑
前端拖拽时,直接更新SortableItem的position字段即可,无需操作各子模型自身的position。通过section.sorted_items可以获取全局排序后的所有子项列表。
为什么不能直接复用原有结构?
acts_as_list的scope机制是基于单模型设计的,它只能在同一模型的数据集内维护position的连续性和唯一性。跨模型时,不同模型的position序列完全独立,无法实现全局排序的需求。多态中间层将位置管理抽离为独立逻辑,所有子模型共享同一个位置序列,是最合理的解决方案。
内容的提问来源于stack exchange,提问作者Dennis Martinez
相关产品推荐
相关产品推荐

