am-rails
am-rails

Reputation: 1473

How to Structure Multiple Levels of Hierarchy in Ruby on Rails?

A rails app contains many different Pages of content. The Pages are organized into small groups known as Sections:

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

The Sections need to be organized too, but what is the best way to do this - re-use sections themselves, or create a new Unit model?

Option 1 - Re-use Section
Allow Section to have children and parent Sections. That way, you don't need to create another model with similar fields as Section. Some sections will have_many pages, and other sections will have_many children sections:

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

Option 2 - new Unit model
Create another model called Unit to organize the sections. It will have many similar fields to section, but it will be a clearly separate entity.

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

The advantage of Option 1 is it avoid some duplication, and could be adapted in the future if even more levels are needed. However, Option 2 clearly separates the roles of Sections, which have_many pages, from Units, which have_many Sections, which can help keep other code clear. Which is the best approach?

Update
It seems Option 2 would have clearer code, such as when going through all the Sections. Is it worth re-using Sections if it would make some code more complicated? For example, here's how to list all the Sections in an organized manner:

Option 2 - For each Unit, list all the child sections. Then list any Sections that aren't in any Unit.

Option 1 - For each parent Section, list all the children Sections. Then list any Section that has with no parent Section or child Section.

Upvotes: 5

Views: 2112

Answers (4)

kddeisz
kddeisz

Reputation: 5182

It really depends on how far you want to go. If it's just one extra level of hierarchy, then definitely go with the new model. If you're looking to be able to go 2+ levels deep, definitely go with the option of reusing sections.

Upvotes: 2

Gjaldon
Gjaldon

Reputation: 5644

It will be worth reusing Section(using Option 1), if you see Section and its children having exactly the same methods defined in them. Otherwise, you should go for Option 2.

Regarding the concerns you had on how to list all the Sections in an organized manner:

Option 1 - This isn't and can be done unless you want to iterate through one collection which has the parent section and the children sections. See how we could do some of the queries in ActiveRecord below:

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)

Option 2 - Below are some code for comparison with above:

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)

As you can see, both options will have very similar code for listing children and parents so that shouldn't be a concern when deciding on what option to go for.

Upvotes: 3

Daniel
Daniel

Reputation: 7172

You don't have to use a relational database for all of your data storage.

Mongodb (mongoid: http://mongoid.org/en/mongoid/index.html) may be a good solution to your problem.

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

Upvotes: 0

DiegoSalazar
DiegoSalazar

Reputation: 13521

I would go with the nested set of sections using awesome_nested_set. By going this route you reduce the number of database calls needed to get a section and all of its child sections. The Unit class doesn't do much other than group sections and seems like it also duplicates columns common to section e.g. title... Another thing to note is if your requirements include the ability to have arbitrarily deep nested sections. With the Unit approach you're stuck at 1 level deep.

Upvotes: 1

Related Questions