如何在Ruby on Rails中构建多层次的层级结构?

5

一款Rails应用包含许多不同的内容页面。这些页面被组织成小组,称为“部分”:

class Page < ActiveRecord::Base
   attr_accessible: section_id #etc..
   belongs_to :section
end

class Section < ActiveRecord::Base
  attr_accessible :title #, etc... 
  has_many :pages
end

需要对章节进行组织,但是最好的方法是重用章节本身,还是创建一个新的单元模型呢?

选项1 - 重用章节
允许章节有子章节和父章节。这样,您就不需要创建另一个具有类似字段的模型作为章节。一些章节将具有许多页面,而其他章节将具有许多子章节:

class Section < ActiveRecord::Base
  attr_accessible :parent_id  :title # etc... 
  has_many :pages

  belongs_to :parent, class_name: "Section"
  has_many :children, class_name: "Section", foreign_key: "parent_id"
end

选项2 - 新的Unit模型
创建另一个名为Unit的模型来组织章节。它将具有许多与section相似的字段,但它将是一个明确分离的实体。

class Section < ActiveRecord::Base
  attr_accessible :title, :unit_id # etc... 
  has_many :pages
  belongs_to :units
end

class Unit < ActiveRecord::Base
  attr_accessible :title # etc... 
  has_many :sections
end

选项1的优点是避免了一些重复,并且如果需要更多级别,可以在未来进行调整。然而,选项2明确分离了具有许多页面的部分角色和具有许多部分的单元角色,这有助于保持其他代码的清晰度。哪种方法最好?
更新 看起来选项2会有更清晰的代码,例如当浏览所有章节时。如果重复使用章节会使某些代码变得更加复杂,那么它是否值得?例如,以下是如何以组织方式列出所有章节的方法:
选项2-对于每个单元,列出所有子章节。然后列出任何不属于任何单元的章节。
选项1-对于每个父章节,列出所有子章节。然后列出没有父章节或子章节的任何章节。

章节可以出现在不同的页面上,这是有意为之吗?而且一个页面只有一个章节?我本来以为应该是相反的 :) 噢等等:您没有呈现页面,一个章节有许多页面,但您的父级/容器例如是一个“书”?一本书有许多章节,每个章节都有许多页面? - nathanvda
@nathanvda,页面是最小的单位,许多页面组织在一起形成节。 "单元"可以是一个更大的单元,用于容纳节,类似于教科书中的单元。 - am-rails
4个回答

3
如果您发现某个章节及其子章节中定义的方法完全相同,那么重复使用该章节(使用选项1)将是值得的。否则,您应该选择选项2。
关于您对如何以有组织的方式列出所有章节的担忧:
选项1 - 这不是不可能做到,除非您想遍历一个包含父节和子节的集合。请参见下面我们如何在ActiveRecord中执行一些查询的示例:
sections_with_parent = Section.joins(:parent)
sections_with_children = Section.joins(:children).uniq
parent_key_with_children_values = Section.joins(:children).uniq.inject({}) do |result, section|
  result.merge({section => section.children})
end
sections_with_no_parent = Section.where(parent_id: nil)

选项2 - 以下是一些与上面进行比较的代码:
sections_with_parent = Section.joins(:unit)
units_with_children = Unit.joins(:sections).uniq
parent_key_with_children_values = Unit.joins(:sections).uniq.inject({}) do |result, unit|
  result.merge({unit => unit.sections })
end
sections_with_no_parent = Section.where(unit_id: nil)

如您所见,两种选择在列出子项和父项时的代码非常相似,因此在决定选择哪个选项时不必担心。


可以通过以下方式遍历视图中的单元和部分:Unit.all.each do |unit| <%= unit.name %> unit.sections.each do |section| <%= section.name %> end end当遍历父级和子级部分时,这可能会更加混乱。 - am-rails
我理解你的观点。然而,如果在Unit和Section模型中有很多重复的行为,那么在视图中使用Section.where(section_id: nil).each do |section|或者Section.children do |child|的工作量相对于重复的代码来说是微不足道的。所以这真的取决于你是否认为Section和Unit模型将具有类似的行为。 - Gjaldon

2

这真的取决于您想要到达的程度。如果只是一个额外的层级,那么一定要选择新模型。如果您想要达到两个或更多层级的深度,请选择重用部分的选项。


1
我会选择使用嵌套设置部分,使用awesome_nested_set。通过这种方式,您可以减少获取部分及其所有子部分所需的数据库调用次数。Unit类除了分组部分之外并没有太多作用,而且似乎也复制了与部分相同的列,例如标题...另一件需要注意的事情是,如果您的要求包括具有任意深度的嵌套部分的能力。使用Unit方法,您只能固定在1个级别。

0

并不是所有的数据存储都必须使用关系型数据库。

Mongodb(mongoid:http://mongoid.org/en/mongoid/index.html)可能是解决您问题的好方法。

class Page
  include Mongoid::Document

  embeds_many :sections, :class_name => 'Sections', :inverse_of => :page
end

class Section
  include Mongoid::Document
  field :title, :type => String, :default => ''

  embedded_in :page, :class_name => 'Page', :inverse_of => :sections
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接